38 Commits
Author SHA1 Message Date
Fuzzlemann 6216b0c01e Bump version to 2.2 2020-07-11 23:09:54 +02:00
Fuzzlemann 901ce0cfeb Fix naming of the static final field 2020-07-11 23:09:12 +02:00
Fuzzlemann a537099297 Fixes TCPShield on servers using Java versions higher than or equal to 12 2020-07-11 23:08:01 +02:00
Fuzzlemann 7a14a45338 Removes the modified logger on Bungee 2020-07-09 23:45:14 +02:00
Fuzzlemann 8f35cf9532 Fixes forced hosts in the Velocity implementation when the player is e.g. a forge user 2020-07-06 21:02:37 +02:00
Paul Zhang 39a977fa97 Merge pull request #8 from Fuzzlemann/pr
TCPShield v2.1
2020-07-06 18:02:25 +02:00
Fuzzlemann 2178727b21 Adds the timestamp validation mode to the warning 2020-07-06 17:58:49 +02:00
Fuzzlemann 553332c641 Bumps version to 2.1 2020-07-06 17:53:18 +02:00
Fuzzlemann 23c207e3b9 Temporarily disables native Paper support 2020-07-06 17:52:02 +02:00
Fuzzlemann bf40833d5d Fixes forced hosts with Velocity 2020-07-02 22:14:41 +02:00
Fuzzlemann 818df3cbb1 Changes Velocity player disconnect when the handshake is legacy as an error was thrown 2020-06-30 20:59:53 +02:00
Fuzzlemann fcc3ef4fef Fixes Velocity error messages on legacy pings 2020-06-25 18:11:50 +02:00
Ryan De Lap d39f520279 Add contributors section to README 2020-06-14 22:20:15 -05:00
Ryan De Lap d24a6a1a99 Merge pull request #6 from Fuzzlemann/pr
Major rewrite of TCPShield
2020-06-14 02:05:06 -05:00
Fuzzlemann c3b5d209ae Fixes a bug where the config generation on BungeeCord failed. 2020-06-13 00:58:13 +02:00
Fuzzlemann 2ca823411f Fix for the Bungee timeout issue many are facing.
This simple but mandatory commit prevents that the hostname of the server is resolved to its IP which can cause massive network latencies.
2020-06-11 02:25:34 +02:00
Fuzzlemann 197f908fc5 Fixes wrongly named packets 2020-05-28 16:57:23 +02:00
Fuzzlemann a784224592 Updates README.md 2020-05-27 17:00:13 +02:00
Fuzzlemann 57f3e37ff7 Revert "Update README.md"
This reverts commit 3dc3fcad
2020-05-27 16:40:48 +02:00
Fuzzlemann f4b56671b9 Changes server to google.com 2020-05-24 02:18:29 +02:00
Fuzzlemann ed0670c5c5 Implements automatic synchronization of time over HTTP 2020-05-24 00:34:34 +02:00
Fuzzlemann 801fca080a Adds an option for turning on / off timestamp validation in case someone does not have control over the host machine time 2020-05-23 22:35:21 +02:00
Fuzzlemann 00b79770bd Fixes mistakenly named bungeecord implementation of IConfig 2020-05-15 23:03:03 +02:00
Fuzzlemann 9aef0402f5 Adds testing and fixes some issues which were noticed while creating the tests, including:
- correct exception handling when verifying signatures
- also searching private fields when searching fields by class
- making modifier field accessible
2020-05-15 22:52:13 +02:00
Fuzzlemann 1b76e0e6bf Adds support for Velocity 2020-05-15 19:43:11 +02:00
Fuzzlemann 43bbf589de Fix config creation error with BungeeCord 2020-05-15 19:25:47 +02:00
Fuzzlemann 12b8f8da99 removes forgotten debug messages 2020-05-15 18:34:09 +02:00
Fuzzlemann 9cd06036bb Adds native paper support (does not require the installation of ProtocolLib) 2020-05-15 18:32:32 +02:00
Fuzzlemann d9e2e34196 Merge remote-tracking branch 'fork/master' 2020-05-13 00:56:51 +02:00
Fuzzlemann f6bf0346ff -> changes class names related to abstraction 2020-05-13 00:56:37 +02:00
Paul Zhang 3dc3fcad05 Update README.md 2020-05-13 00:05:04 +02:00
Fuzzlemann 60ae269f33 Major code restructuring in order to fulfill quality requirements
-> moves to Gradle
  -> major speedup of build times
  -> automatic adjustment of version in bungee.yml and plugin.yml based on version given in the gradle build file
-> implements abstraction layers
  -> unification of Bungee & Bukkit logic
   -> helps keeping code up-to-date and similar (e.g. timestamp checks were disabled on bukkit but enabled on bungee)
   -> removes boilerplate code which is vulnerable for bugs
-> adds own and specific exceptions
-> improves/changes debug/log messages
-> i.g. code improvements / code style changes
-> some performance improvements
2020-05-12 23:59:46 +02:00
Steven Ferguson b8921033d1 Update version number 2020-05-12 03:59:21 -06:00
Steven Ferguson 92c27ed407 Update version number 2020-05-12 03:58:46 -06:00
RyanDeLap d4e41d2c28 Revert tinyprotocol changes, generic extra hostname info 2020-05-11 21:11:14 -05:00
Ryan De Lap 751bcafcc6 Merge pull request #4 from TCPShield/timestamp-valdiation
Update to block legacy ping
2020-04-28 08:50:56 -05:00
RyanDeLap a641acdfb6 Small cleanup 2020-04-28 08:50:45 -05:00
RyanDeLap c89705c0c8 Update to block legacy ping 2020-04-28 08:48:37 -05:00
57 changed files with 1897 additions and 613 deletions

No files matched your search

+25 -6
View File
@@ -1,13 +1,32 @@
# RealIP
Bungee/Spigot plugin which parses client IP addresses passed from the TCPShield network.
# TCPShield
TCPShield is the plugin for the same named DDoS mitigation service [TCPShield](https://tcpshield.com).
This plugin is responsible for validating clients join via the TCPShield network.
It also parses passed IP addresses so the server is aware of the real player IP address.
### Compatibility
TCPShield is compatible with Paper, Spigot / CraftBukkit, BungeeCord and Velocity.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed. This is not necessary when Paper is being used.
### Setup
Setting up the plugin is easy as cake. Please follow [these](https://docs.tcpshield.com/onboarding-1/tcpshield-plugin) guidelines.
### Compiling
To compile this project, install maven and run the following command:
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
```
mvn clean install
gradle build
```
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
Please contact [@RyanDeLap](https://t.me/RyanDeLap) on Telegram for all support related to this product.
See [Contact](https://docs.tcpshield.com/about-us)
Before contact, ensure you have tested with no Bungee Plugins installed. Plugins like BungeePluginManager will break this plugin!
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Fuzzlemann](https://github.com/Fuzzlemann)
* [RyanDeLap](https://github.com/RyanDeLap)
+151
View File
@@ -0,0 +1,151 @@
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.yaml', name: 'snakeyaml', version: '1.26'
classpath group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
}
}
plugins {
id 'java'
id 'idea'
}
repositories {
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/'
}
}
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.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'
}
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.2'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
}
void updateYaml() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
options.setPrettyFlow(true)
def yaml = new Yaml(options)
def bukkitYamlFile = new File('src/main/resources/plugin.yml')
def bungeeYamlFile = new File('src/main/resources/bungee.yml')
def files = [bukkitYamlFile, bungeeYamlFile]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
def cfg = yaml.load(inputStream)
cfg.put("version", version)
new FileWriter(file).withCloseable { writer ->
yaml.dump(cfg, writer)
}
}
}
}
void updateJson() {
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
new InputStreamReader(inputStream, StandardCharsets.UTF_8).withCloseable { inputStreamReader ->
JsonObject object = JsonParser.parseReader(inputStreamReader).getAsJsonObject()
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(object.toString())
}
}
}
}
}
build.dependsOn updateVersion
-85
View File
@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tcpshield.realip</groupId>
<artifactId>RealIP</artifactId>
<version>1.4-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>RealIP</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>*.yml</include>
<include>*.key</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
@@ -1,192 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Handshake.Client;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.StructureModifier;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class PacketHandler
extends PacketAdapter {
private String properField = null;
private final Logger logger;
private final boolean onlyProxy;
private final boolean debugMode;
public PacketHandler(Logger logger, boolean onlyProxy, boolean debugMode) {
super(TCPShieldBukkit.getInstance(), new PacketType[]{PacketType.Handshake.Client.SET_PROTOCOL});
this.logger = logger;
this.onlyProxy = onlyProxy;
this.debugMode = debugMode;
}
public void onPacketReceiving(PacketEvent event) {
boolean proxyConnection = false;
String raw = null;
try {
raw = (String) event.getPacket().getStrings().read(0);
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (signature.contains("%%%")) {
signature = signature.split("%%%", 2)[0];
}
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
Object injector = ReflectionUtils.getPrivateField(ignored.getClass(), ignored, "injector");
Object networkManager = ReflectionUtils.getPrivateField(injector.getClass(), injector, "networkManager");
if (this.properField == null) {
this.properField = ReflectionUtils.getProperField(networkManager.getClass());
}
Channel channel = (Channel) ReflectionUtils.getPrivateField(injector.getClass(), injector, "originalChannel");
InetSocketAddress newRemoteAddress = new InetSocketAddress(host, port);
proxyConnection = true;
try {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, this.properField == null ? "l" : this.properField, newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
ReflectionUtils.setFinalField(AbstractChannel.class, channel, "remoteAddress", newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
event.getPacket().getStrings().write(0, hostname);
}
}
} catch (Exception ex) {
Player player;
ex.printStackTrace();
} finally {
if ((this.onlyProxy) && (!proxyConnection)) {
Player player = event.getPlayer();
if(this.debugMode) {
this.logger.warning("Disconnecting " + player.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if(raw != null) {
this.logger.warning(raw);
}
player.kickPlayer("");
}
}
}
private Channel getChannel(Player player) {
try {
Class<? extends Player> playerClass = player.getClass();
Object playerHandle = playerClass.getMethod("getHandle", new Class[0]).invoke(player, new Object[0]);
Object playerConnection = getNmsClass("EntityPlayer").getField("playerConnection").get(playerHandle);
Object networkManager = getNmsClass("PlayerConnection").getField("networkManager").get(playerConnection);
return (Channel) getNmsClass("NetworkManager").getField("channel").get(networkManager);
} catch (Exception e) {
throw new RuntimeException("Couldn't get player channel", e);
}
}
private Class getNmsClass(String name)
throws ClassNotFoundException {
return Class.forName(getNmsPackage() + "." + name);
}
private String getNmsPackage() {
String obcString = Bukkit.getServer().getClass().getPackage().getName();
String versionString = obcString.substring(obcString.lastIndexOf('.') + 1);
return "net.minecraft.server." + versionString;
}
public static class ReflectionUtils {
private static final Field FIELD_MODIFIERS;
private static Map<Class<?>, Map<String, Field>> cachedFields = new HashMap();
public static void setFinalField(Class objectClass, Object object, String fieldName, Object value)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
FIELD_MODIFIERS.setInt(field, field.getModifiers() & 0xFFFFFFEF);
}
field.set(object, value);
}
public static Object getPrivateField(Class objectClass, Object object, String fieldName)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
return field.get(object);
}
public static String getProperField(Class objectClass) {
for (Field f : objectClass.getFields()) {
if (f.getType() == SocketAddress.class) {
return f.getName();
}
}
return "N/A";
}
private static Field getDeclaredField(Class<?> clazz, String fieldName) {
if (!cachedFields.containsKey(clazz)) {
cachedFields.put(clazz, new HashMap());
}
Map<String, Field> clazzCache = (Map) cachedFields.get(clazz);
if (clazzCache.containsKey(fieldName)) {
return (Field) clazzCache.get(fieldName);
}
try {
Field field = clazz.getDeclaredField(fieldName);
clazzCache.put(fieldName, field);
return field;
} catch (NoSuchFieldException e) {
clazzCache.put(fieldName, null);
throw new RuntimeException(e);
}
}
static {
Field field = null;
try {
field = getDeclaredField(Field.class, "modifiers");
field.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
FIELD_MODIFIERS = field;
}
}
}
@@ -1,36 +0,0 @@
package net.tcpshield.realip;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Signing {
private static PublicKey publicKey;
public static void init()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(Signing.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public static boolean verify(byte[] data, String signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
}
}
@@ -1,48 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Logger;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit
extends JavaPlugin {
private static TCPShieldBukkit INSTANCE;
private static Boolean stopping = Boolean.valueOf(false);
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
INSTANCE = this;
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
saveDefaultConfig();
FileConfiguration config = getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProtocolLibrary.getProtocolManager().addPacketListener(new PacketHandler(getLogger(), this.onlyProxy, this.debugMode));
}
public void onDisable() {
stopping = Boolean.valueOf(true);
INSTANCE = null;
}
public static TCPShieldBukkit getInstance() {
if (stopping.booleanValue()) {
throw new IllegalAccessError("Plugin is disabling!");
}
return INSTANCE;
}
}
@@ -1,239 +0,0 @@
package net.tcpshield.realip;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.event.EventHandler;
public class TCPShieldBungee
extends Plugin
implements Listener {
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
saveDefaultConfig();
Configuration config = loadConfig();
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProxyServer proxy = getProxy();
PluginManager pluginManager = proxy.getPluginManager();
pluginManager.registerListener(this, this);
Logger logger = ProxyServer.getInstance().getLogger();
Logger newLogger = new Logger("BungeeCord", null) {
public void log(Level level, String msg, Object param1) {
if ((msg.equals("{0} has connected")) && (param1.getClass().getSimpleName().equals("InitialHandler"))) {
return;
}
super.log(level, msg, param1);
}
};
newLogger.setParent(logger);
try {
set(ProxyServer.getInstance(), "logger", newLogger);
} catch (Exception e) {
e.printStackTrace();
}
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
private void saveDefaultConfig() {
File dataFolder = getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdir();
}
File file = getConfigFile();
if (!file.exists()) {
try {
InputStream in = getResourceAsStream("config.yml");
Throwable localThrowable3 = null;
try {
Files.copy(in, file.toPath(), new CopyOption[0]);
} catch (Throwable localThrowable1) {
localThrowable3 = localThrowable1;
throw localThrowable1;
} finally {
if (in != null) {
if (localThrowable3 != null) {
try {
in.close();
} catch (Throwable localThrowable2) {
localThrowable3.addSuppressed(localThrowable2);
}
} else {
in.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private File getConfigFile() {
File dataFolder = getDataFolder();
return new File(dataFolder, "config.yml");
}
@EventHandler(priority = -64)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
boolean proxyConnection = false;
Channel channel = null;
try {
Field wrapperField = e.getConnection().getClass().getDeclaredField("ch");
wrapperField.setAccessible(true);
Object wrapper = wrapperField.get(e.getConnection());
wrapperField.setAccessible(false);
Field chField = wrapper.getClass().getDeclaredField("ch");
chField.setAccessible(true);
Channel ch = (Channel) chField.get(wrapper);
chField.setAccessible(false);
channel = ch;
if (e.getHandshake().getHost().contains("//")) {
String raw = e.getHandshake().getHost();
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (signature.contains("%%%")) {
signature = signature.split("%%%", 2)[0];
}
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
long currentTime = System.currentTimeMillis() / 1000;
if(!(timestamp >= (currentTime - 2) && timestamp <= (currentTime + 2))) {
if(this.debugMode) {
getLogger().warning("Current time: " + currentTime + ", Timestamp Time: " + timestamp);
}
throw new Exception("Invalid signature timestamp, please check system's local clock if error persists.");
}
hostname = hostname.replace("%%%", "\0");
PendingConnection connection = e.getConnection();
InetSocketAddress sockadd = new InetSocketAddress(host, port);
proxyConnection = true;
try {
set(wrapper.getClass(), wrapper, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "localAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
InetSocketAddress virtualHost = new InetSocketAddress(hostname, e.getHandshake().getPort());
try {
set(connection, "virtualHost", virtualHost);
} catch (Exception ex) {
try {
set(connection, "vHost", virtualHost);
} catch (Exception e2) {
e2.printStackTrace();
}
}
try {
set(e.getHandshake(), "host", hostname);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
} catch (Exception localException) {
localException.printStackTrace();
} finally {
if (this.onlyProxy && !proxyConnection) {
Logger logger = getLogger();
PendingConnection connection = e.getConnection();
if(this.debugMode) {
logger.warning("Disconnecting " + connection.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if (channel != null) {
channel.flush().close();
} else {
connection.disconnect(new BaseComponent[0]);
}
}
}
}
protected static void set(Object main_object, String field, Object value)
throws Exception {
Field f = main_object.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
protected static void set(Class<?> c, Object main_object, String field, Object value)
throws Exception {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
}
@@ -0,0 +1,7 @@
package net.tcpshield.tcpshield;
public class Constants {
public static long MAX_TIME_DIFFERENCE = 3; // in seconds
}
@@ -0,0 +1,122 @@
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.*;
import net.tcpshield.tcpshield.validation.TimestampValidation;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final SignatureVerifier signatureVerifier;
private final TimestampValidation timestampValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.signatureVerifier = new SignatureVerifier();
this.timestampValidation = new TimestampValidation(config);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
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 != 4)
throw new MalformedPayloadException("payload.length != 4. Raw payload = \"" + rawPayload + "\"");
String hostname = payload[0];
String ipData = payload[1];
int timestamp;
try {
timestamp = Integer.parseInt(payload[2]);
} catch (NumberFormatException e) {
throw new MalformedPayloadException(e);
}
String signature = payload[3];
if (!timestampValidation.checkTimestamp(timestamp))
throw new InvalidTimestampException(timestamp, timestampValidation.getTime());
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!signatureVerifier.verify(reconstructedPayload, signature))
throw new SigningVerificationFailureException();
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidTimestampException e) {
handleInvalidTimestamp(player, e.getTimestamp(), e.getCurrentTime());
} catch (SigningVerificationFailureException e) {
handleSigningVerificationFailure(player, rawPayload);
} catch (ConnectionNotProxiedException e) {
handleNotProxiedConnection(player, rawPayload);
} catch (IPModificationFailureException e) {
this.logger.warning(String.format("%s[%s/%s]'s IP failed to be modified. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvalidTimestamp(IPlayer player, long timestamp, long currentTime) {
if (config.isDebug()) {
this.logger.warning(String.format("%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(), timestamp, currentTime, config.getTimestampValidationMode()));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
private void handleSigningVerificationFailure(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s[%s/%s] provided valid handshake information, but signing check failed. Raw payload = \"%s\"", player.getName(), player.getUUID(), player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
private void handleNotProxiedConnection(IPlayer player, String rawPayload) {
if (!config.isOnlyProxy()) return;
if (config.isDebug()) {
this.logger.info(String.format("%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(), rawPayload));
}
player.disconnect();
}
}
@@ -0,0 +1,99 @@
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 = HashBasedTable.create();
private static final Field MODIFIERS_FIELD;
static {
Field tempModifiersField;
try {
tempModifiersField = 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);
tempModifiersField = Arrays.stream(fields).filter(field -> field.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
MODIFIERS_FIELD = tempModifiersField;
MODIFIERS_FIELD.setAccessible(true);
}
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())) {
MODIFIERS_FIELD.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) {
for (Field f : clazz.getDeclaredFields()) {
if (f.getType() == searchFor) {
return f;
}
}
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.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.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,41 @@
package net.tcpshield.tcpshield;
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;
public class SignatureVerifier {
private final PublicKey publicKey;
public SignatureVerifier() throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(SignatureVerifier.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public boolean verify(String str, String signature) {
return verify(str.getBytes(StandardCharsets.UTF_8), signature);
}
private boolean verify(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) { // catching for illegal base64 encoding or invalid encoding for signatures
return false;
}
}
}
@@ -0,0 +1,9 @@
package net.tcpshield.tcpshield.abstraction;
public interface IPacket {
String getRawPayload();
void modifyOriginalPacket(String hostname) throws Exception;
}
@@ -0,0 +1,19 @@
package net.tcpshield.tcpshield.abstraction;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.net.InetSocketAddress;
public interface IPlayer {
String getUUID();
String getName();
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
void disconnect();
}
@@ -0,0 +1,21 @@
package net.tcpshield.tcpshield.abstraction;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected String timestampValidationMode;
protected boolean debug;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
}
public boolean isDebug() {
return this.debug;
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit extends JavaPlugin {
@Override
public void onEnable() {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
new TCPShieldProtocolLib(this).load();
}
}
private boolean isPaper() {
if (true) return false; // disabled for now
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,18 @@
package net.tcpshield.tcpshield.bukkit.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -0,0 +1,36 @@
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 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(JavaPlugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
}
}
@@ -0,0 +1,18 @@
package net.tcpshield.tcpshield.bukkit.paper;
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() {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(plugin);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
}
}
@@ -0,0 +1,24 @@
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);
}
}
@@ -0,0 +1,46 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
import java.util.UUID;
public class PaperPlayerImpl implements IPlayer {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayerImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getUUID() {
UUID uuid = handshakeEvent.getUniqueId();
if (uuid == null) return "unknown";
return uuid.toString();
}
@Override
public String getName() {
return "unknown";
}
@Override
public String getIP() {
return handshakeEvent.getSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.setFailMessage("Connection failed. Please try again or contract an administrator.");
handshakeEvent.setFailed(true);
}
}
@@ -0,0 +1,31 @@
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.impl.BukkitConfigImpl;
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) {
super(plugin, ListenerPriority.HIGHEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
}
@Override
public void onPacketReceiving(PacketEvent e) {
IPacket packet = new ProtocolLibPacketImpl(e.getPacket());
IPlayer player = new ProtocolLibPlayerImpl(e.getPlayer());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,23 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
private final JavaPlugin plugin;
public TCPShieldProtocolLib(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
plugin.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
}
}
@@ -0,0 +1,25 @@
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);
}
}
@@ -0,0 +1,66 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import io.netty.channel.AbstractChannel;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Field socketAddressField;
private final Player player;
private String ip;
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
return "unknown"; // not supported with temporary players
}
@Override
public String getName() {
return "unknown"; // not supported with temporary players
}
@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");
if (socketAddressField == null) {
socketAddressField = ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class);
}
ReflectionUtils.setFinalField(networkManager, socketAddressField, ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(AbstractChannel.class, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -0,0 +1,39 @@
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.api.plugin.Plugin;
import net.md_5.bungee.event.EventHandler;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bungee.impl.BungeeConfigImpl;
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(Plugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BungeeConfigImpl(plugin));
}
@EventHandler(priority = -64)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy()) return;
connection.disconnect();
}
@EventHandler(priority = -64)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,14 @@
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;
public class TCPShieldBungee extends Plugin implements Listener {
@Override
public void onEnable() {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(this));
}
}
@@ -0,0 +1,57 @@
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.timestampValidationMode = config.getString("timestamp-validation");
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);
}
}
}
@@ -0,0 +1,36 @@
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);
}
}
@@ -0,0 +1,60 @@
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.net.InetSocketAddress;
import java.util.UUID;
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 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 IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
ReflectionUtils.setFinalField(channelWrapper, "remoteAddress", ip);
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();
}
}
@@ -0,0 +1,16 @@
package net.tcpshield.tcpshield.exception;
public class ConnectionNotProxiedException extends SecurityException {
public ConnectionNotProxiedException() {
super();
}
public ConnectionNotProxiedException(Throwable cause) {
super(cause);
}
public ConnectionNotProxiedException(String s) {
super(s);
}
}
@@ -0,0 +1,8 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,20 @@
package net.tcpshield.tcpshield.exception;
public class InvalidTimestampException extends SigningVerificationFailureException {
private final long timestamp;
private final long currentTime;
public InvalidTimestampException(long timestamp, long currentTime) {
this.timestamp = timestamp;
this.currentTime = currentTime;
}
public long getTimestamp() {
return timestamp;
}
public long getCurrentTime() {
return currentTime;
}
}
@@ -0,0 +1,15 @@
package net.tcpshield.tcpshield.exception;
public class MalformedPayloadException extends ConnectionNotProxiedException {
public MalformedPayloadException() {
}
public MalformedPayloadException(Throwable cause) {
super(cause);
}
public MalformedPayloadException(String s) {
super(s);
}
}
@@ -0,0 +1,8 @@
package net.tcpshield.tcpshield.exception;
public class SigningVerificationFailureException extends MalformedPayloadException {
public SigningVerificationFailureException() {
super();
}
}
@@ -0,0 +1,12 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(String message) {
super(message);
}
}
@@ -0,0 +1,11 @@
package net.tcpshield.tcpshield.validation;
public interface ITimestampValidator {
boolean validateTimestamp(long checkedTimestamp);
default long getTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -0,0 +1,34 @@
package net.tcpshield.tcpshield.validation;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.validation.impl.DisabledTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.HTPDateTimestampValidatorImpl;
import net.tcpshield.tcpshield.validation.impl.SystemTimeTimestampValidatorImpl;
public class TimestampValidation {
private final ITimestampValidator timestampValidator;
public TimestampValidation(TCPShieldConfig config) {
switch (config.getTimestampValidationMode().toLowerCase()) {
case "system":
this.timestampValidator = new SystemTimeTimestampValidatorImpl();
break;
case "off":
this.timestampValidator = new DisabledTimestampValidatorImpl();
break;
case "htpdate":
default:
this.timestampValidator = new HTPDateTimestampValidatorImpl();
}
}
public boolean checkTimestamp(long timestamp) {
return timestampValidator.validateTimestamp(timestamp);
}
public long getTime() {
return timestampValidator.getTime();
}
}
@@ -0,0 +1,13 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
/**
* @author Fuzzlemann
*/
public class DisabledTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
return true;
}
}
@@ -0,0 +1,81 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
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;
/**
* inspired by https://github.com/iridium77/htpdate/blob/master/htpdate.c
*/
public class HTPDateTimestampValidatorImpl implements ITimestampValidator {
private volatile long timestampOffset = 0;
public HTPDateTimestampValidatorImpl() {
ForkJoinPool.commonPool().submit(() -> {
try {
syncTimestamp();
} catch (Exception e) {
e.printStackTrace();
}
}); // do not execute network requests on the main thread
}
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
private void syncTimestamp() throws IOException {
try (Socket socket = new Socket("google.com", 80)) {
// no-cache forces the server to return time
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 = extractDate(response);
timestampOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
}
private Date extractDate(List<String> response) {
for (String line : response) {
if (!line.startsWith("Date: ")) continue;
String dateString = line.substring("Date: ".length());
SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.ENGLISH);
try {
return sdf.parse(dateString);
} catch (ParseException e) {
throw new IllegalStateException(e);
}
}
throw new IllegalArgumentException("no date line found - response: " + response);
}
@Override
public long getTime() {
return (System.currentTimeMillis() + timestampOffset) / 1000;
}
}
@@ -0,0 +1,14 @@
package net.tcpshield.tcpshield.validation.impl;
import net.tcpshield.tcpshield.Constants;
import net.tcpshield.tcpshield.validation.ITimestampValidator;
public class SystemTimeTimestampValidatorImpl implements ITimestampValidator {
@Override
public boolean validateTimestamp(long checkedTimestamp) {
long currentTime = getTime();
return checkedTimestamp >= (currentTime - Constants.MAX_TIME_DIFFERENCE) && checkedTimestamp <= (currentTime + Constants.MAX_TIME_DIFFERENCE);
}
}
@@ -0,0 +1,34 @@
package net.tcpshield.tcpshield.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import java.nio.file.Path;
import java.util.logging.Logger;
@Plugin(id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity {
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;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
server.getEventManager().register(this, new VelocityHandshakePacketHandler(logger, dataFolder.toFile()));
}
}
@@ -0,0 +1,47 @@
package net.tcpshield.tcpshield.velocity;
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.VelocityConfigImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPacketImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPlayerImpl;
import java.io.File;
import java.util.logging.Logger;
public class VelocityHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public VelocityHandshakePacketHandler(Logger logger, File dataFolder) {
this.handshakePacketHandler = new HandshakePacketHandler(logger, new VelocityConfigImpl(dataFolder));
}
@Subscribe
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe
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,45 @@
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.timestampValidationMode = toml.getString("timestamp-validation");
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());
}
}
@@ -0,0 +1,72 @@
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;
}
}
@@ -0,0 +1,84 @@
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 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 {
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection"), "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.getProtocolVersion().isUnknown() || inboundConnection.getProtocolVersion().isLegacy();
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
return "unknown"; // not supported
}
@Override
public String getName() {
return "unknown"; // not supported
}
@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
}
}
}
+3 -3
View File
@@ -1,4 +1,4 @@
name: TCPShield
version: 1.6
main: net.tcpshield.realip.TCPShieldBungee
author: 'https://tcpshield.com'
version: '2.2'
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
+8
View File
@@ -0,0 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
+3
View File
@@ -1,5 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections: true
# available modes: system (uses the system time), htpdate (uses a synchronized date) & off (deactivates timestamp validation)
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
+5 -4
View File
@@ -1,5 +1,6 @@
name: TCPShield
version: 1.6
main: net.tcpshield.realip.TCPShieldBukkit
depend: ['ProtocolLib']
author: 'https://tcpshield.com'
version: '2.2'
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
author: https://tcpshield.com
+11
View File
@@ -0,0 +1,11 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.0",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -0,0 +1,74 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.impl.TestConfigImpl;
import net.tcpshield.tcpshield.impl.TestPacketImpl;
import net.tcpshield.tcpshield.impl.TestPlayerImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl("system");
private final TCPShieldConfig skippingTimestampCheckTestConfig = new TestConfigImpl("off");
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
private final HandshakePacketHandler skippingTimestampCheckHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), skippingTimestampCheckTestConfig);
private TestPlayerImpl player;
@BeforeEach
public void setUp() {
player = new TestPlayerImpl();
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testSuccessfulFMLTaggedHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testUnsuccessfulHandshakeFailedTimestampCheck() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@Test
public void testUnsuccessfulHandshakeMalformedPayload() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_MALFORMED_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
@Test
public void testUnsuccessfulHandshakeInvalidSignature() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
assertFalse(player.isConnected());
}
}
@@ -0,0 +1,102 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReflectionUtilsTest {
private static final String MODIFIED = "modified";
private static final String ORIGINAL = "original";
private ReflectionUtilsTestObject testObject;
@BeforeEach
public void resetTestObject() {
testObject = new ReflectionUtilsTestObject(ORIGINAL);
}
@Test
public void testSetPublicField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "publicString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicString());
}
@Test
public void testSetPrivateField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "privateString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateString());
}
@Test
public void testSetPublicFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "publicFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicFinalString());
}
@Test
public void testSetPrivateFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "privateFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateFinalString());
}
@Test
public void testSearchFieldByClass() {
Field field = ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, ReflectionUtilsTestObject.class);
assertEquals("testObject", field.getName());
}
@Test
public void testSearchFieldByNonExistentClass() {
Assertions.assertThrows(IllegalArgumentException.class, () -> ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, byte.class));
}
@Test
public void testGetObjectInPrivateField() throws NoSuchFieldException, IllegalAccessException {
String privateField = (String) ReflectionUtils.getObjectInPrivateField(testObject, "privateString");
assertEquals(ORIGINAL, privateField);
}
public static class ReflectionUtilsTestObject {
public final String publicFinalString;
private final String privateFinalString;
public String publicString;
private String privateString;
public ReflectionUtilsTestObject(String str) {
this.publicFinalString = str;
this.privateFinalString = str;
this.publicString = str;
this.privateString = str;
}
public String getPublicFinalString() {
return publicFinalString;
}
public String getPrivateFinalString() {
return privateFinalString;
}
public String getPublicString() {
return publicString;
}
public void setPublicString(String publicString) {
this.publicString = publicString;
}
public String getPrivateString() {
return privateString;
}
public void setPrivateString(String privateString) {
this.privateString = privateString;
}
}
}
@@ -0,0 +1,60 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class SignatureObjectVerifierTest {
private static SignatureVerifier signatureVerifier;
@BeforeAll
public static void setUp() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureVerifier = new SignatureVerifier();
}
@Test
public void testVerificationSuccess() {
SignatureObject signatureObject = createSignature(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP);
assertTrue(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
@Test
public void testVerificationFailure() {
SignatureObject signatureObject = createSignature(TestConstants.INVALID_PAYLOAD_INVALID_SIGNATURE);
assertFalse(signatureVerifier.verify(signatureObject.getReconstructedPayload(), signatureObject.getSignature()));
}
private SignatureObject createSignature(String payload) {
int signatureIndex = payload.lastIndexOf("///");
String signature = payload.substring(signatureIndex + 3);
String reconstructedPayload = payload.substring(0, signatureIndex);
return new SignatureObject(signature, reconstructedPayload);
}
private static class SignatureObject {
private final String signature;
private final String reconstructedPayload;
private SignatureObject(String signature, String reconstructedPayload) {
this.signature = signature;
this.reconstructedPayload = reconstructedPayload;
}
public String getSignature() {
return signature;
}
public String getReconstructedPayload() {
return reconstructedPayload;
}
}
}
@@ -0,0 +1,16 @@
package net.tcpshield.tcpshield;
public class TestConstants {
public static final String STANDARD_HOSTNAME = "example.org";
public static final String PAYLOAD_HOSTNAME = "homeserver.fuzzlemann.de";
public static final String PAYLOAD_FML_HOSTNAME = "homeserver.fuzzlemann.de\0FML\0";
public static final String PAYLOAD_IP = "79.227.33.179";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD_INVALID_TIMESTAMP + "\0FML\0";
public static final String INVALID_PAYLOAD_INVALID_SIGNATURE = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTsEXCEPTION";
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "malformed_payload";
}
@@ -0,0 +1,12 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl(String timestampValidationMode) {
this.timestampValidationMode = timestampValidationMode;
this.debug = true;
this.onlyProxy = true;
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class TestPacketImpl implements IPacket {
private final String payload;
private String hostname;
public TestPacketImpl(String payload, String hostname) {
this.payload = payload;
this.hostname = hostname;
}
@Override
public String getRawPayload() {
return payload;
}
@Override
public void modifyOriginalPacket(String hostname) {
this.hostname = hostname;
}
public String getHostname() {
return hostname;
}
}
@@ -0,0 +1,46 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class TestPlayerImpl implements IPlayer {
private InetSocketAddress ip;
private boolean connected;
public TestPlayerImpl() {
this.connected = true;
}
@Override
public String getUUID() {
return "uuid";
}
@Override
public String getName() {
return "name";
}
@Override
public String getIP() {
if (ip == null) return "ip";
return ip.getAddress().getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
this.ip = ip;
}
@Override
public void disconnect() {
connected = false;
}
public boolean isConnected() {
return connected;
}
}