54 Commits
Author SHA1 Message Date
Draylar 740e648b92 TCPShield Fabric support (#40)
Adds Fabric support (huge, huge thanks to @Draylar)
2021-03-09 18:35:31 +01:00
Paul Zhang dd51276790 Updates outdated docs link 2021-02-27 16:21:33 +01:00
Paul Zhang 8f88b928e8 Update README.md 2021-02-27 16:20:44 +01:00
Paul Zhang 5b76160f78 Finish Paper native support 2021-02-27 16:12:57 +01:00
Paul Zhang d872880ede Remove IPlayer#getUUID and IPlayer#getName and their corresponding implementations because these informations aren't available at this state yet 2021-02-25 19:18:26 +01:00
Paul Zhang 07b80c0cfa remove debug message 2021-02-25 19:14:47 +01:00
Paul Zhang f567b6add3 Version bump yml + toml changes + the floodgate 2.0 jar 2021-02-25 19:13:13 +01:00
Paul Zhang a1654ea9af Bump version to 2.5 2021-02-25 18:47:49 +01:00
Paul Zhang 7b4543e744 Update Floodgate 2021-02-25 18:47:16 +01:00
Paul Zhang c9d088c0d0 Rough implementation of Geyser support. It only supports Floodgate 2.0 in conjunction with @mikroskreem's PROXY v2 PR.
!It's not yet working properly!
2021-02-25 18:45:21 +01:00
Paul Zhang b215435e03 Finish Geyser support 2021-02-25 18:13:32 +01:00
Paul Zhang 10d78caa56 Paper x Geyser 2021-02-25 14:48:56 +01:00
Paul Zhang 4b6aaf573f Revert "Removes Paper native support"
This reverts commit d3dd1d7a20.
2021-02-25 14:22:21 +01:00
Paul Zhang d3dd1d7a20 Removes Paper native support 2021-02-25 14:21:10 +01:00
Paul Zhang b80369562e Rough implementation of Geyser support. It only supports Floodgate 2.0 in conjunction with @mikroskreem's PROXY v2 PR.
!It's not yet working properly!
2021-02-25 14:21:04 +01:00
Paul Zhang 44715187c4 fixes spelling mistake 2021-02-25 13:31:03 +01:00
Paul Zhang 75e30ec6b3 we're not in rust 2021-02-24 14:53:24 +01:00
Paul Zhang f0f1c53ba8 Lazily initialize the final field 2021-02-23 18:20:32 +01:00
Paul Zhang 5779306940 Merge pull request #33 from SlingSMP/master
Remove Legacy API
2021-01-19 13:59:58 +01:00
Ben Kerllenevich d031708308 Remove Legacy 2021-01-19 06:40:40 -05:00
Paul Zhang 46f7e9385b Merge pull request #32 from TCPShield/bugfix/event-priority
Adjusts event priority
2021-01-11 23:44:48 +01:00
Paul Zhang f100c0578a adjust event priority 2021-01-08 23:54:30 +01:00
Paul Zhang 15c0543114 removes two unused fields 2021-01-08 23:38:13 +01:00
Paul Zhang a1d6664906 removes second [TCPShield]-prefix on ip whitelist fetching 2020-12-27 00:34:52 +01:00
Paul Zhang 22b0b7065d Merge pull request #28 from TCPShield/feature-revise-gitignore
Update .gitignore
2020-12-24 17:13:02 +01:00
Paul Zhang e66f4df4ee Merge pull request #29 from RoboMWM/patch-1
contract an administrator
2020-12-23 20:04:36 +01:00
RoboMWM 74bc2b069c Well, contracts can be kewl too 2020-12-23 11:01:40 -08:00
Paul Zhang 637c64916b Update .gitignore 2020-12-23 17:14:43 +01:00
Paul Zhang 0a8d6295b2 Merge pull request #27 from TCPShield/bugfix-http-ok-check
Adds a check if the webserver returns 200 on pulling the IP list
2020-12-23 16:58:41 +01:00
Paul Zhang 1d7f2c368a changes line formatting 2020-12-23 16:55:05 +01:00
Paul Zhang 086bdd99d5 adds additional checks for validity of the returned string 2020-12-23 16:54:04 +01:00
Paul Zhang d01aa5efc4 Adds a check if the webserver returns 200 on pulling the IP list 2020-12-23 16:43:16 +01:00
Paul Zhang d67ece7a0d Create LICENSE 2020-12-19 20:07:06 +01:00
Paul Zhang 615e4b2289 Update README.md
Updates user link
2020-12-12 01:18:23 +01:00
Paul Zhang ddf722c069 Fixes testing 2020-12-06 20:25:18 +01:00
Fuzzlemann 1b716d5e18 Merge remote-tracking branch 'origin/master' 2020-12-04 15:21:56 +01:00
Fuzzlemann 839d625ce9 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:21:47 +01:00
Fuzzlemann 8a01e915b1 Fixes that even though the IP is whitelisted, the player is being disconnected if the host packet didn't originate from the TCPShield network 2020-12-04 15:20:30 +01:00
Fuzzlemann 32d422a952 Fixes raw IP matching 2020-12-04 13:45:22 +01:00
Fuzzlemann c5cd43f2fa Merge remote-tracking branch 'origin/master' 2020-12-02 14:12:51 +01:00
Fuzzlemann d69d4f7ba8 Removes unused config keys 2020-12-02 14:12:35 +01:00
Paul Zhang 52325a8a2c Merge pull request #24 from TCPShield/ip-validation
TCPShield v2.4
2020-12-01 23:22:08 +01:00
Fuzzlemann b07fe80979 Future-proofs HandshakePacketHandler for possible proxy-side changes in the handshake hostname data 2020-12-01 23:13:39 +01:00
Fuzzlemann c6471d8e15 Fixes compatibility on some BungeeCord forks like FlameCord 2020-12-01 23:11:48 +01:00
Fuzzlemann 0dd3c25421 Removes the empty line on fetching the TCPShield IP list 2020-12-01 22:14:23 +01:00
Fuzzlemann 42530caa57 Reverts re-activation of native Paper support 2020-11-30 17:11:53 +01:00
Fuzzlemann e58319cabe Bumps version to v2.4 2020-11-30 17:11:23 +01:00
Fuzzlemann 1de480d575 Merge remote-tracking branch 'origin/ip-validation' into ip-validation 2020-11-29 19:34:30 +01:00
Fuzzlemann 1959106ac8 Fixes that the updated IPs are appended instead of replaced 2020-11-29 19:33:50 +01:00
Paul Zhang 5750391e43 Update README.md
Updated because of the re-introduction of native paper support
2020-11-29 15:30:00 +01:00
Fuzzlemann 00f50110fe Adds checks for the origin IP (if it's originating from TCPShield nodes) in replacement of signature checks + timestamp validation. Custom CIDR records can be added by creating a file in the "ip-whitelist" folder and adding one CIDR entry per line. 2020-11-29 15:16:11 +01:00
Paul Zhang e14d937a70 Merge pull request #16 from JasperJH/master
Fixes searchFieldByClass looping forever and incorrect cache keys
2020-11-05 23:19:10 +01:00
Fuzzlemann 3d5dab69bb Changes wrong static final field naming 2020-11-01 19:09:00 +01:00
JasperJH 78d22e627c Fixes searchFieldByClass looping forever and incorrect cache keys 2020-09-11 12:12:16 +02:00
70 changed files with 993 additions and 563 deletions

No files matched your search

+12 -3
View File
@@ -1,4 +1,13 @@
# Testing
ip-whitelist/
# IntelliJ
.idea/
*/target/
*/*/target/
*.iml
build/
*.iml
# Gradle
.gradle/
gradle/
gradlew
gradlew.bat
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 TCPShield
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+5 -3
View File
@@ -6,12 +6,13 @@ It also parses passed IP addresses so the server is aware of the real player IP
### Compatibility
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord and Velocity.
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.
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 cake. Please follow [these](https://docs.tcpshield.com/onboarding/tcpshield-plugin) guidelines.
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/config/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:
@@ -28,5 +29,6 @@ See [Contact](https://docs.tcpshield.com/about-us)
These wonderful contributors have helped TCPShield make this plugin better!
* [Fuzzlemann](https://github.com/Fuzzlemann)
* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
+21 -5
View File
@@ -1,3 +1,4 @@
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
@@ -18,6 +19,7 @@ buildscript {
plugins {
id 'java'
id 'idea'
id 'fabric-loom' version '0.6-SNAPSHOT'
}
repositories {
@@ -39,6 +41,12 @@ repositories {
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = 'https://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
}
dependencies {
@@ -47,7 +55,7 @@ dependencies {
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'
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'
@@ -55,9 +63,16 @@ dependencies {
// 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'
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 {
@@ -68,7 +83,7 @@ configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.3'
version = '2.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
@@ -133,6 +148,7 @@ void updateYaml() {
}
void updateJson() {
def gson = new GsonBuilder().setPrettyPrinting().create();
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
@@ -143,7 +159,7 @@ void updateJson() {
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(object.toString())
writer.write(gson.toJson(object))
}
}
}
Binary file not shown.
+10
View File
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
@@ -1,7 +0,0 @@
package net.tcpshield.tcpshield;
public class Constants {
public static long MAX_TIME_DIFFERENCE = 3; // in seconds
}
@@ -3,24 +3,25 @@ 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 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 SignatureVerifier signatureVerifier;
private final TimestampValidation timestampValidation;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.signatureVerifier = new SignatureVerifier();
this.timestampValidation = new TimestampValidation(config);
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
@@ -31,6 +32,12 @@ public class HandshakePacketHandler {
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
@@ -44,55 +51,34 @@ public class HandshakePacketHandler {
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length != 4)
throw new MalformedPayloadException("payload.length != 4. Raw payload = \"" + rawPayload + "\"");
if (payload.length < 2) return; // is nonetheless allowed as it passed the IP check
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 (InvalidIPException e) {
handleInvalidIPException(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));
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 handleInvalidTimestamp(IPlayer player, long timestamp, long currentTime) {
private void handleInvalidIPException(IPlayer player, String rawPayload) {
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()));
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()) {
@@ -100,23 +86,8 @@ public class HandshakePacketHandler {
}
}
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();
}
public TCPShieldConfig getConfig() {
return config;
}
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();
}
}
@@ -14,27 +14,7 @@ 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 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);
}
private static Field modifiersField;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
@@ -43,7 +23,25 @@ public class ReflectionUtils {
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);
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);
@@ -73,16 +71,16 @@ public class ReflectionUtils {
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
while (currentClass.getSuperclass() != null) {
for (Field field : clazz.getDeclaredFields()) {
if (field.getType() != searchFor) continue;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = clazz.getSuperclass();
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
@@ -1,41 +0,0 @@
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;
}
}
}
@@ -6,10 +6,6 @@ import java.net.InetSocketAddress;
public interface IPlayer {
String getUUID();
String getName();
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
@@ -1,21 +1,28 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected String timestampValidationMode;
protected File ipWhitelistFolder;
protected boolean debug;
protected boolean geyser;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public String getTimestampValidationMode() {
return this.timestampValidationMode;
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
return this.debug;
}
public boolean isGeyser() {
return this.geyser;
}
}
@@ -13,8 +13,7 @@ public class TCPShieldBukkit extends JavaPlugin {
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 to enable native compatibility or install ProtocolLib.");
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Please install ProtocolLib in order to use our plugin.");
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;
}
@@ -22,13 +21,17 @@ public class TCPShieldBukkit extends JavaPlugin {
}
}
/**
* 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() {
if (true) return false; // disabled for now
try {
Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
Class<?> handshakeEvent = Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
handshakeEvent.getMethod("getOriginalSocketAddressHostname");
return true;
} catch (ClassNotFoundException e) {
} catch (ClassNotFoundException | NoSuchMethodException e) {
return false;
}
}
@@ -4,6 +4,8 @@ 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) {
@@ -12,7 +14,8 @@ public class BukkitConfigImpl extends TCPShieldConfig {
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -8,6 +8,8 @@ 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;
@@ -17,11 +19,11 @@ public class PaperHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public PaperHandshakePacketHandler(JavaPlugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
public PaperHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.HIGHEST)
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
@@ -29,8 +31,13 @@ public class PaperHandshakePacketHandler implements Listener {
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.HIGHEST)
@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,5 +1,11 @@
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;
@@ -12,7 +18,11 @@ public class TCPShieldPaper {
}
public void load() {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(plugin);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(handshakePacketHandler);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
});
}
}
@@ -1,10 +1,10 @@
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;
import java.util.UUID;
public class PaperPlayerImpl implements IPlayer {
@@ -14,22 +14,9 @@ public class PaperPlayerImpl implements IPlayer {
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();
return handshakeEvent.getOriginalSocketAddressHostname();
}
@Override
@@ -40,7 +27,7 @@ public class PaperPlayerImpl implements IPlayer {
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.setFailMessage("Connection failed. Please try again or contract an administrator.");
handshakeEvent.failMessage(Component.text("Connection failed. Please try again or contact an administrator."));
handshakeEvent.setFailed(true);
}
}
@@ -0,0 +1,23 @@
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);
}
}
@@ -0,0 +1,30 @@
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);
}
}
@@ -7,7 +7,6 @@ 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;
@@ -16,9 +15,9 @@ 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));
public ProtocolLibHandshakePacketHandler(JavaPlugin plugin, HandshakePacketHandler handshakePacketHandler) {
super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = handshakePacketHandler;
}
@Override
@@ -1,5 +1,9 @@
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 {
@@ -11,7 +15,10 @@ public class TCPShieldProtocolLib {
}
public void load() {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin);
com.comphenix.protocol.ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin, handshakePacketHandler);
ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
});
}
}
@@ -8,14 +8,11 @@ import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
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 static Field remoteAddressField;
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
@@ -37,16 +34,6 @@ public class ProtocolLibPlayerImpl implements IPlayer {
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;
@@ -4,13 +4,11 @@ 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.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.BungeeConfigImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePacketImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePlayerImpl;
@@ -18,8 +16,8 @@ public class BungeeHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public BungeeHandshakePacketHandler(Plugin plugin) {
this.handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BungeeConfigImpl(plugin));
public BungeeHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.LOWEST)
@@ -3,12 +3,19 @@ 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.geyser.GeyserUtils;
public class TCPShieldBungee extends Plugin implements Listener {
@Override
public void onEnable() {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(this));
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.getLogger(), new BungeeConfigImpl(this));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(handshakePacketHandler));
});
}
}
@@ -22,7 +22,8 @@ public class BungeeConfigImpl extends TCPShieldConfig {
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = config.getString("timestamp-validation");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
@@ -9,7 +9,6 @@ import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.UUID;
public class BungeePlayerImpl implements IPlayer {
@@ -21,19 +20,6 @@ public class BungeePlayerImpl implements IPlayer {
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;
@@ -1,16 +0,0 @@
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,4 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends SecurityException {
}
@@ -1,20 +0,0 @@
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;
}
}
@@ -1,15 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class MalformedPayloadException extends ConnectionNotProxiedException {
public MalformedPayloadException() {
}
public MalformedPayloadException(Throwable cause) {
super(cause);
}
public MalformedPayloadException(String s) {
super(s);
}
}
@@ -1,8 +0,0 @@
package net.tcpshield.tcpshield.exception;
public class SigningVerificationFailureException extends MalformedPayloadException {
public SigningVerificationFailureException() {
super();
}
}
@@ -0,0 +1,16 @@
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);
}
}
@@ -0,0 +1,18 @@
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.");
}
}
@@ -0,0 +1,105 @@
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;
}
}
}
@@ -0,0 +1,24 @@
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
}
}
@@ -0,0 +1,40 @@
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."));
}
}
@@ -0,0 +1,20 @@
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();
}
@@ -0,0 +1,11 @@
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();
}
@@ -0,0 +1,32 @@
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,29 @@
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);
});
}
}
@@ -0,0 +1,27 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
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();
}
}
private static boolean useGeyser(TCPShieldConfig config) {
if (!config.isGeyser()) return false;
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -0,0 +1,23 @@
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);
}
}
@@ -0,0 +1,30 @@
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,24 @@
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));
}
}
@@ -0,0 +1,80 @@
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();
}
}
}
@@ -0,0 +1,69 @@
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,11 +0,0 @@
package net.tcpshield.tcpshield.validation;
public interface ITimestampValidator {
boolean validateTimestamp(long checkedTimestamp);
default long getTime() {
return System.currentTimeMillis() / 1000;
}
}
@@ -1,34 +0,0 @@
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,38 @@
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;
}
}
@@ -0,0 +1,59 @@
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;
}
}
@@ -1,13 +0,0 @@
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;
}
}
@@ -1,81 +0,0 @@
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;
}
}
@@ -1,14 +0,0 @@
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);
}
}
@@ -6,6 +6,9 @@ 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.geyser.GeyserUtils;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
import java.nio.file.Path;
import java.util.logging.Logger;
@@ -29,6 +32,8 @@ public class TCPShieldVelocity {
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
server.getEventManager().register(this, new VelocityHandshakePacketHandler(logger, dataFolder.toFile()));
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.logger, new VelocityConfigImpl(dataFolder.toFile()));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> server.getEventManager().register(this, new VelocityHandshakePacketHandler(handshakePacketHandler)));
}
}
@@ -7,19 +7,15 @@ 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));
public VelocityHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@Subscribe(order = PostOrder.FIRST)
@@ -20,7 +20,8 @@ public class VelocityConfigImpl extends TCPShieldConfig {
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = toml.getString("timestamp-validation");
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
this.geyser = toml.getBoolean("enable-geyser-compatibility");
this.debug = toml.getBoolean("debug-mode");
}
@@ -12,7 +12,7 @@ import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
private static final Class<?> initialInboundConnectionClass;
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;
@@ -20,8 +20,8 @@ public class VelocityPlayerImpl implements IPlayer {
static {
try {
initialInboundConnectionClass = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(initialInboundConnectionClass, "connection");
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");
@@ -38,20 +38,10 @@ public class VelocityPlayerImpl implements IPlayer {
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != initialInboundConnectionClass;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
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;
+3 -1
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: '2.3'
version: '2.5'
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softDepends:
- floodgate
+2 -2
View File
@@ -1,8 +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'
# Only set this to true in case you're using TCPShield's Geyser proxy!
enable-geyser-compatibility = false
# Turn on to diagnose connection issues
debug-mode = false
+2 -2
View File
@@ -1,8 +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'
# Only set this to true in case you're using TCPShield's Geyser proxy!
enable-geyser-compatibility: false
# Turn on to diagnose connection issues
debug-mode: false
+27
View File
@@ -0,0 +1,27 @@
{
"schemaVersion": 1,
"id": "tcp-shield",
"version": "2.5",
"name": "TCPShield",
"description": "TCPShield IP parsing capabilities for Fabric",
"authors": [
"TCPShield"
],
"contact": {
"homepage": "https://tcpshield.com"
},
"license": "MIT",
"environment": "*",
"entrypoints": {
"main": [
"net.tcpshield.tcpshield.fabric.TCPShieldFabric"
]
},
"mixins": [
"tcpshield.mixins.json"
],
"depends": {
"fabricloader": "*",
"minecraft": "*"
}
}
+3 -1
View File
@@ -1,6 +1,8 @@
name: TCPShield
version: '2.3'
version: '2.5'
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
- floodgate
author: https://tcpshield.com
api-version: 1.16
Binary file not shown.
+14
View File
@@ -0,0 +1,14 @@
{
"required": true,
"minVersion": "0.8",
"package": "net.tcpshield.tcpshield.fabric.mixin",
"compatibilityLevel": "JAVA_8",
"mixins": [
"ClientConnectionAccessor",
"HandshakeC2SPacketAccessor",
"ServerHandshakeMixin"
],
"injectors": {
"defaultRequire": 1
}
}
+7 -2
View File
@@ -1,11 +1,16 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.3",
"version": "2.5",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [],
"dependencies": [
{
"id": "floodgate",
"optional": true
}
],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -9,27 +9,25 @@ import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl("system");
private final TCPShieldConfig skippingTimestampCheckTestConfig = new TestConfigImpl("off");
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl();
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();
player = new TestPlayerImpl(TestConstants.VALID_IP);
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP, TestConstants.STANDARD_HOSTNAME);
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
@@ -39,36 +37,19 @@ public class HandshakePacketHandlerTest {
public void testSuccessfulFMLTaggedHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
skippingTimestampCheckHandshakePacketHandler.onHandshake(packet, player);
standardHandshakePacketHandler.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);
public void testUnsuccessfulHandshakeInvalidIP() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
TestPlayerImpl player = new TestPlayerImpl(TestConstants.INVALID_IP);
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());
}
}
@@ -1,60 +0,0 @@
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;
}
}
}
@@ -4,13 +4,14 @@ public class TestConstants {
public static final String STANDARD_HOSTNAME = "example.org";
public static final String VALID_IP = "51.161.19.224";
public static final String INVALID_IP = "1.1.1.1";
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 = VALID_PAYLOAD_INVALID_TIMESTAMP + "_INVALID_SIGNATURE";
public static final String INVALID_PAYLOAD_MALFORMED_PAYLOAD = "MALFORMED_PAYLOAD";
public static final String VALID_PAYLOAD = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTs";
public static final String VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED = VALID_PAYLOAD + "\0FML\0";
}
@@ -2,10 +2,12 @@ package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl(String timestampValidationMode) {
this.timestampValidationMode = timestampValidationMode;
public TestConfigImpl() {
this.ipWhitelistFolder = new File("ip-whitelist");
this.debug = true;
this.onlyProxy = true;
}
@@ -2,37 +2,34 @@ package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
public class TestPlayerImpl implements IPlayer {
private InetSocketAddress ip;
private InetAddress ip;
private boolean connected;
public TestPlayerImpl() {
public TestPlayerImpl(String ip) {
this.connected = true;
}
@Override
public String getUUID() {
return "uuid";
}
@Override
public String getName() {
return "name";
try {
this.ip = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public String getIP() {
if (ip == null) return "ip";
return ip.getAddress().getHostAddress();
return ip.getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
this.ip = ip;
this.ip = ip.getAddress();
}
@Override