83 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
Fuzzlemann a4302e4de8 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-11-01 18:20:37 +01:00
Fuzzlemann e859a1c95a Fixes ReflectionUtils#searchFieldByClass cache (#16 by JasperJH) 2020-11-01 18:19:16 +01:00
Fuzzlemann a1e9b84b47 Refactors some test strings in TestConstants.java 2020-11-01 18:17:47 +01:00
Fuzzlemann af998585e9 Adds Maven repository to build.gradle 2020-11-01 18:17:04 +01:00
Fuzzlemann 62b18b0842 Version bump to 2.3 2020-11-01 18:16:46 +01:00
Steven Ferguson 8c0d98fda4 Update README.md 2020-10-30 14:34:47 -06:00
JasperJH 78d22e627c Fixes searchFieldByClass looping forever and incorrect cache keys 2020-09-11 12:12:16 +02:00
Fuzzlemann a6dc0865f7 Fixes some compatibility issues with 1.16.2 where in some cases NetworkManagerServer is passed 2020-08-24 22:16:43 +02:00
Fuzzlemann 7907d8f731 Merge branch 'master' of https://github.com/TCPShield/RealIP 2020-08-13 11:27:28 +02:00
Fuzzlemann 32142f9961 Fixes some exceptions regarding legacy connections on Velocity 1.7 clients 2020-08-13 11:27:03 +02:00
Paul Zhang f8d32f5e96 Update README.md 2020-08-11 15:11:46 +02:00
Fuzzlemann f8da84de07 Fixes an IllegalArgumentException because of a missing SocketAddress field on some BungeeCord versions (= 1.7, e.g. zBungeeCord) 2020-08-11 13:19:40 +02:00
Fuzzlemann cafc278c32 Fixes 1.7 support on some BungeeCord versions 2020-08-10 15:10:25 +02:00
Fuzzlemann e958718bcb Fixes issues with compatibility on 1.7 2020-07-30 19:42:49 +02:00
Fuzzlemann 031c2ed091 Change -64 to the constant EventPriority.LOWEST 2020-07-25 13:03:01 +02:00
Fuzzlemann faf7568c81 Add Velocity event orders (priorities) to ensure better compatibility 2020-07-15 17:17:48 +02:00
Fuzzlemann 812f5c353f Fixes error message when ProtocolLib's not installed 2020-07-15 17:14:43 +02:00
Fuzzlemann 6216b0c01e Bump version to 2.2 2020-07-11 23:09:54 +02:00
Fuzzlemann 901ce0cfeb Fix naming of the static final field 2020-07-11 23:09:12 +02:00
Fuzzlemann a537099297 Fixes TCPShield on servers using Java versions higher than or equal to 12 2020-07-11 23:08:01 +02:00
Fuzzlemann 7a14a45338 Removes the modified logger on Bungee 2020-07-09 23:45:14 +02:00
Fuzzlemann 8f35cf9532 Fixes forced hosts in the Velocity implementation when the player is e.g. a forge user 2020-07-06 21:02:37 +02:00
Paul Zhang 39a977fa97 Merge pull request #8 from Fuzzlemann/pr
TCPShield v2.1
2020-07-06 18:02:25 +02:00
Fuzzlemann 2178727b21 Adds the timestamp validation mode to the warning 2020-07-06 17:58:49 +02:00
Fuzzlemann 553332c641 Bumps version to 2.1 2020-07-06 17:53:18 +02:00
Fuzzlemann 23c207e3b9 Temporarily disables native Paper support 2020-07-06 17:52:02 +02:00
Fuzzlemann bf40833d5d Fixes forced hosts with Velocity 2020-07-02 22:14:41 +02:00
Fuzzlemann 818df3cbb1 Changes Velocity player disconnect when the handshake is legacy as an error was thrown 2020-06-30 20:59:53 +02:00
Fuzzlemann fcc3ef4fef Fixes Velocity error messages on legacy pings 2020-06-25 18:11:50 +02:00
Ryan De Lap d39f520279 Add contributors section to README 2020-06-14 22:20:15 -05:00
72 changed files with 1115 additions and 582 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.
+13 -4
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 Paper, 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 is not necessary when Paper is being used.
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-1/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:
@@ -22,4 +23,12 @@ gradle build
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
See [Contact](https://docs.tcpshield.com/about-us)
See [Contact](https://docs.tcpshield.com/about-us)
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
+24 -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,9 +19,13 @@ buildscript {
plugins {
id 'java'
id 'idea'
id 'fabric-loom' version '0.6-SNAPSHOT'
}
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
@@ -36,6 +41,12 @@ repositories {
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = 'https://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
}
dependencies {
@@ -44,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'
@@ -52,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 {
@@ -65,7 +83,7 @@ configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.0'
version = '2.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
@@ -130,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]
@@ -140,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.", player.getName(), player.getUUID(), player.getIP(), timestamp, currentTime));
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();
}
}
@@ -2,24 +2,46 @@ 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 Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static Field modifiersField;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException, NoSuchFieldException {
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
Field modifiersField = getDeclaredField(Field.class, "modifiers");
modifiersField.setAccessible(true);
modifiersField.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);
@@ -45,21 +67,41 @@ public class ReflectionUtils {
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
for (Field f : clazz.getDeclaredFields()) {
if (f.getType() == searchFor) {
return f;
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field cachedField = CACHED_FIELDS.get(clazz, fieldName);
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field = clazz.getDeclaredField(fieldName);
CACHED_FIELDS.put(clazz, fieldName, field);
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw e;
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -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;
}
}
@@ -2,6 +2,7 @@ package net.tcpshield.tcpshield.bukkit;
import net.tcpshield.tcpshield.bukkit.paper.TCPShieldPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.TCPShieldProtocolLib;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit extends JavaPlugin {
@@ -11,15 +12,26 @@ public class TCPShieldBukkit extends JavaPlugin {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper (1.16 build #503 or higher) to enable native compatibility or install ProtocolLib.");
return;
}
new TCPShieldProtocolLib(this).load();
}
}
/**
* Checks if the server version supports native Paper support (Paper 1.16.5 build #503 or higher)
*
* @return if the server version supports native Paper support
*/
private boolean isPaper() {
try {
Class.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
@@ -39,7 +26,8 @@ public class PaperPlayerImpl implements IPlayer {
@Override
public void disconnect() {
handshakeEvent.setFailMessage("Connection failed. Please try again or contract an administrator.");
handshakeEvent.setCancelled(false);
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,6 +1,9 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import org.bukkit.Bukkit;
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 {
@@ -12,12 +15,10 @@ public class TCPShieldProtocolLib {
}
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);
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin, handshakePacketHandler);
ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
});
}
}
@@ -2,37 +2,38 @@ 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 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 Class<?> abstractChannelClass;
private final Player player;
private String ip;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String 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;
@@ -46,14 +47,11 @@ public class ProtocolLibPlayerImpl implements IPlayer {
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);
ReflectionUtils.setFinalField(networkManager, ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
@@ -4,12 +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;
@@ -17,11 +16,11 @@ 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 = -64)
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy()) return;
@@ -29,7 +28,7 @@ public class BungeeHandshakePacketHandler implements Listener {
connection.disconnect();
}
@EventHandler(priority = -64)
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
@@ -1,36 +1,21 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.ProxyServer;
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.ReflectionUtils;
import java.util.logging.Level;
import java.util.logging.Logger;
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));
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 {
ReflectionUtils.setField(ProxyServer.getInstance(), "logger", newLogger);
} catch (Exception e) {
e.printStackTrace();
}
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");
}
@@ -6,8 +6,9 @@ import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.util.UUID;
import java.net.SocketAddress;
public class BungeePlayerImpl implements IPlayer {
@@ -19,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;
@@ -45,7 +33,13 @@ public class BungeePlayerImpl implements IPlayer {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
ReflectionUtils.setFinalField(channelWrapper, "remoteAddress", ip);
try {
Field socketAddressField = ReflectionUtils.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtils.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
@@ -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);
}
}
@@ -5,4 +5,8 @@ public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(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)));
}
}
@@ -1,41 +1,42 @@
package net.tcpshield.tcpshield.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
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
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
private void handleEvent(InboundConnection connection) {
IPlayer player = new VelocityPlayerImpl(connection);
VelocityPlayerImpl player = new VelocityPlayerImpl(connection);
if (player.isLegacy()) {
player.disconnect();
return;
}
IPacket packet = new VelocityPacketImpl(connection);
handshakePacketHandler.onHandshake(packet, player);
@@ -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");
}
@@ -11,11 +11,15 @@ 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 {
HANDSHAKE_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection"), "handshake");
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);
}
@@ -40,7 +44,29 @@ public class VelocityPacketImpl implements IPacket {
@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;
}
}
@@ -12,13 +12,17 @@ import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
static {
try {
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection"), "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");
REMOTE_ADDRESS_FIELD = ReflectionUtils.getPrivateField(minecraftConnection, "remoteAddress");
@@ -29,27 +33,24 @@ public class VelocityPlayerImpl implements IPlayer {
}
private final InboundConnection inboundConnection;
private final boolean legacy;
private String ip;
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public String getUUID() {
return "unknown"; // not supported
}
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();
@@ -65,7 +66,8 @@ public class VelocityPlayerImpl implements IPlayer {
@Override
public void disconnect() {
try {
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
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 -1
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: '2.0'
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.0'
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.0",
"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 = "homeserver.fuzzlemann.de///79.227.33.179:53462///1589573485///MGQCMHFac1If9Y8vGAkZMTVapmC6l4M7EfD0YJ0b3pQy73IFjEPsOQn+yxp9lAKSZz7X3AIwGJ2rSxGu4JvlW5sm0pyzqUrc6mfd0xHUWC9JkusrSBSKDfiFV7TCmSZc0gGivgTsEXCEPTION";
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