29 Commits
Author SHA1 Message Date
Dylan Keir e20f2df589 Merge pull request #128 from lewisgibson/htpdate-socket
Close the HTPDate socket and time out its connection
2026-10-01 23:21:57 +01:00
Dylan Keir b2fdd4dc4e Merge pull request #127 from lewisgibson/spigot-version-and-timestamp
Fix Spigot version lookup and parse handshake timestamps as long
2026-10-01 23:21:17 +01:00
Dylan Keir d348727586 Merge pull request #126 from lewisgibson/cidr-matching-fixes
Fix CIDR mask validation and address matching
2026-10-01 23:20:13 +01:00
Dylan Keir b5df9d9427 Merge pull request #125 from lewisgibson/cidr-validator-thread-safe
Make the CIDR validator cache thread safe
2026-10-01 23:18:17 +01:00
Dylan Keir 2cf6b46c5d Merge pull request #123 from lewisgibson/folia-supported
Mark the plugin as Folia supported
2026-10-01 23:16:43 +01:00
Lewis Gibson a608a4c168 Close the HTPDate socket and time out its connection
The socket was never closed and had no connect or read timeout, so an unreachable or stalled google.com left a ForkJoinPool common pool thread blocked indefinitely. Both now give up after 10 seconds, and when google.com cannot be reached the server's own clock is used, as before.
2026-10-01 19:33:31 +01:00
Lewis Gibson f6030d24d6 Use the Spigot API to read the server version
Server#getMinecraftVersion is Paper only, so with prefer-protocollib enabled on Spigot onEnable failed with a NoSuchMethodError, which the surrounding catch (Exception) does not handle (#107). The fallback to the Paper handler for ProtocolLib 5.2.1 and below now also requires the Paper handshake event, since Spigot cannot load that handler.
2026-10-01 19:32:22 +01:00
Lewis Gibson 6f1820cb6d Parse the handshake timestamp as a long
Unix time in seconds no longer fits in an int after 19 January 2038, at which point every signed handshake would be rejected with a NumberFormatException.
2026-10-01 19:32:21 +01:00
Lewis Gibson 62658147a3 Add CIDR matcher tests for mask edges and address families 2026-10-01 19:31:16 +01:00
Lewis Gibson 3172f51728 Reject CIDR masks that are longer than the address
A mask such as 203.0.113.0/33 was accepted and then threw an ArrayIndexOutOfBoundsException from match() for the address it names, and a negative mask matched on a meaningless prefix. Reject both when the entry is loaded, where invalid entries are already skipped.
2026-10-01 19:31:15 +01:00
Lewis Gibson ef77cd7bcd Fix CIDR matching for IPv6 prefixes of 32 bits
A mask of 32 was always treated as an exact address match, which is only right for IPv4. An IPv6 entry such as 2001:db8::/32 matched nothing but 2001:db8::. Compare the mask against the address length instead.
2026-10-01 19:31:14 +01:00
Lewis Gibson 5bed462a2a Fix CIDR entries without a mask being dropped
The mask was always read from the split entry, so a bare address such as 203.0.113.7 threw an ArrayIndexOutOfBoundsException and was ignored with a warning. Also reject empty entries, which would otherwise resolve to the loopback address.
2026-10-01 19:31:13 +01:00
Lewis Gibson ef7b7c6510 Make the CIDR validator cache thread safe
validate() is called from a network thread per handshake and added to a plain HashSet, which is not safe to modify concurrently.
2026-10-01 19:30:11 +01:00
Lewis Gibson 82d05ea316 Mark the plugin as Folia supported
The Paper handshake path (PlayerHandshakeEvent) uses no scheduler and no world access.
2026-10-01 19:28:53 +01:00
Dylan Keir b316629e04 Bump version to 2.8.1 & fix issues with ProtocolLib on 1.21 2024-08-04 13:06:24 +01:00
Dylan Keir 554d6c300e 2.8.0 - Bump required Java verison from 8 -> 17, warn 1.20.5/1.20.6 users about incompatible ProtocolLib versions, also disable prefer-protocollib by default in favor of Paper handler 2024-06-01 05:29:39 +01:00
Dylan Keir 133cef037e 2.7.0 - Update to support Velocity's new class name 2024-01-22 22:55:59 +00:00
Dylan Keir bb7511d37f Ignore new ServerData packet that was throwing errors 2022-09-19 15:41:34 -04:00
Dylan Keir 78666de764 Merge pull request #86 from gp-Airee/patch-1
Correct typo in handshake failure kick
2022-07-20 19:52:27 +01:00
Graham 0534b8eae8 Contract An Administrator 2022-07-20 20:49:58 +02:00
Dylan Keir 1af7f1efde 2.6.0, too many changes to list, will be in release readme 2022-06-28 03:29:13 +01:00
Dylan Keir 41ff3f412c (2.5.6) re-introduce PreLoginEvent 2022-02-02 18:52:53 +00:00
Dylan Keir cfbd6ffac5 Support Velocity's new LoginInboundConnection class. 2021-11-21 17:13:47 +00:00
Dylan Keir cf97e37632 Add try/catch to java version check 2021-10-31 15:43:08 +00:00
Dylan Keir 1629334e71 fix issue with signature verification due to erroneous event, 2.5.4 release 2021-10-27 22:17:57 +01:00
Dylan Keir 8cb66fc87a add description to plugin.yml 2021-10-11 13:30:30 +01:00
Dylan Keir e868bab503 Geyser support with BungeeCord & Velocity is now fully functional 2021-10-11 13:25:28 +01:00
Dylan Keir 2daad65b33 fix 2021-08-28 05:49:38 +01:00
Dylan Keir 846f4d0a6e 2.5.4: initial geyser/floodgate v2 support with only-allow-proxy-connections 2021-08-28 05:43:47 +01:00
37 changed files with 571 additions and 128 deletions

No files matched your search

+4 -1
View File
@@ -10,4 +10,7 @@ build/
.gradle/
gradle/
gradlew
gradlew.bat
gradlew.bat
libs/
+1
View File
@@ -28,6 +28,7 @@ See [Contact](https://tcpshield.com/#contact)
These wonderful contributors have helped TCPShield make this plugin better!
* [Dylan Keir](https://github.com/DylanKeir)
* [Paul Zhang](https://github.com/paulzhng)
* [RyanDeLap](https://github.com/RyanDeLap)
* [PlumpOrange](https://github.com/xPlumpOrange/)
+13 -16
View File
@@ -5,7 +5,6 @@ import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
// --
// Building
// --
@@ -24,15 +23,13 @@ plugins {
id 'idea'
}
// --
// Variables
// --
version = '2.5.3'
version = '2.8.1'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
// --
// Misc.
// --
@@ -63,12 +60,11 @@ sourceSets {
}
compileJava {
sourceCompatibility = "8"
targetCompatibility = "8"
sourceCompatibility = 17
targetCompatibility = 17
options.encoding = 'UTF-8'
}
// --
// Dependencies
// --
@@ -86,31 +82,32 @@ repositories {
url = 'https://oss.sonatype.org/content/repositories/snapshots'
}
maven {
url = 'https://papermc.io/repo/repository/maven-public/'
url = 'https://repo.papermc.io/repository/maven-public/'
}
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
url = "https://repo.opencollab.dev/maven-snapshots/"
}
}
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '4.4.0'
// compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '5.3.0-SNAPSHOT'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.15.2-R0.1-SNAPSHOT'
compileOnly(files("libs/ProtocolLib.jar")) // Temporary, currently using build #726 from the CI server since the maven repository is out of date.
// Paper - 1.19.4 so we can bump from JDK 8 -> JDK 17, using 1.20.x as a dependency would require bumping from JDK 8 -> JDK 21
compileOnly group: 'io.papermc.paper', name: 'paper-api', version: '1.19.4-R0.1-SNAPSHOT'
// BungeeCord
compileOnly group: 'net.md-5', name: 'bungeecord-api', version: '1.14-SNAPSHOT'
// Velocity
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '3.3.0-SNAPSHOT'
// Testing
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'
compileOnly 'org.geysermc.floodgate:api:2.1.1-SNAPSHOT'
}
processResources {
@@ -176,4 +173,4 @@ void updateJsons() {
}
}
build.dependsOn updateVersion
build.dependsOn updateVersion
@@ -1,22 +1,26 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.PacketProvider;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.exception.parse.InvalidPayloadException;
import net.tcpshield.tcpshield.util.exception.parse.SignatureValidationException;
import net.tcpshield.tcpshield.util.exception.parse.TimestampValidationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InvalidSecretException;
import net.tcpshield.tcpshield.util.validation.SignatureValidator;
import net.tcpshield.tcpshield.util.validation.cidr.CIDRValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import net.tcpshield.tcpshield.util.validation.timestamp.impl.HTPDateTimestampValidator;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
/**
* Packet handler for TCPShield's plugins
@@ -31,6 +35,7 @@ public class TCPShieldPacketHandler {
/**
* Creates an instance of the TCPShieldPacketHandler
*
* @param plugin The TCPShield plugin
*/
public TCPShieldPacketHandler(TCPShieldPlugin plugin) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
@@ -39,12 +44,12 @@ public class TCPShieldPacketHandler {
initValidators();
}
/**
* Initiates the validators
*
* @throws NoSuchAlgorithmException SignatureValidator exception
* @throws IOException SignatureValidator exception
* @throws InvalidKeySpecException SignatureValidator exception
* @throws IOException SignatureValidator exception
* @throws InvalidKeySpecException SignatureValidator exception
*/
private void initValidators() throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
signatureValidator = new SignatureValidator();
@@ -78,6 +83,7 @@ public class TCPShieldPacketHandler {
* Handles the packet and player manipulation
* for the initial handshake from TCPShield
* servers
*
* @param packet The handshake packet
* @param player The involved player
*/
@@ -86,43 +92,57 @@ public class TCPShieldPacketHandler {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
String extraData = null;
String cleanedPayload;
int nullIndex;
if ((nullIndex = packet.getPayloadString().indexOf('\0')) != -1) { // FML tagged payload
cleanedPayload = packet.getPayloadString().substring(0, nullIndex);
extraData = packet.getPayloadString().substring(nullIndex);
} else // Standard payload
cleanedPayload = packet.getPayloadString();
String[] payload = cleanedPayload.split("///", 4);
String[] payload = packet.getPayloadString().split("///");
if (payload.length != 4)
if (cidrValidator.validate(inetAddress))
return; // Allow connection with no processing
else
throw new InvalidPayloadException();
throw new InvalidPayloadException("length: " + payload.length + ", payload: " + Arrays.toString(payload) + ", raw payload: " + packet.getPayloadString());
int nullIndex;
if ((nullIndex = payload[3].indexOf('\0')) != -1) { // FML tagged payload
String originalData = payload[3];
payload[3] = originalData.substring(0, nullIndex);
extraData = originalData.substring(nullIndex);
}
String hostname = payload[0];
String ipData = payload[1];
int timestamp = Integer.parseInt(payload[2]);
long timestamp = Long.parseLong(payload[2]);
String signature = payload[3];
if (!timestampValidator.validate(timestamp))
throw new TimestampValidationException(timestampValidator, timestamp);
String[] ipParts;
String host;
int port;
if (timestamp == 0 && GeyserUtils.GEYSER_SUPPORT_ENABLED) {
// Remap the altered layout
ipData = payload[0];
signature = payload[1];
hostname = payload[3];
// This is annoying having to have this in both blocks but w/e
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
if (!signature.equals(GeyserUtils.SESSION_SECRET)) {
throw new InvalidSecretException("Invalid secret: " + signature);
}
} else {
ipParts = ipData.split(":");
host = ipParts[0];
port = Integer.parseInt(ipParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!timestampValidator.validate(timestamp))
throw new TimestampValidationException(timestampValidator, timestamp);
String[] ipParts = ipData.split(":");
String host = ipParts[0];
int port = Integer.parseInt(ipParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!signatureValidator.validate(reconstructedPayload, signature))
throw new SignatureValidationException();
if (!signatureValidator.validate(reconstructedPayload, signature))
throw new SignatureValidationException();
}
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
@@ -163,6 +183,14 @@ public class TCPShieldPacketHandler {
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (InvalidSecretException e) {
plugin.getDebugger().warn(
"%s[%s/%s] provided an invalid session secret when authenticating a geyser connection.",
player.getName(), player.getUUID(), player.getIP());
if (plugin.getConfigProvider().isOnlyProxy())
player.disconnect();
throw new HandshakeException(e);
} catch (Exception e) {
if (plugin.getConfigProvider().isOnlyProxy())
@@ -12,24 +12,28 @@ public interface TCPShieldPlugin {
/**
* Gets the plugin's config provider
*
* @return The plugin's config provider
*/
ConfigProvider getConfigProvider();
/**
* Gets the plugin's logger
*
* @return The plugin's logger
*/
Logger getLogger();
/**
* Gets the packet handler
*
* @return The packet handler
*/
TCPShieldPacketHandler getPacketHandler();
/**
* Gets the plugin's debugger
*
* @return The plugin's debugger
*/
Debugger getDebugger();
@@ -40,12 +44,15 @@ public interface TCPShieldPlugin {
*/
default void initialization() {
String jvmVersion = System.getProperty("java.version");
// Java 11 check/warning (Will eventually force Java 16 without warning)
String[] versionParts = jvmVersion.split("\\.");
int baseVersion = Integer.parseInt(versionParts[0]);
if (baseVersion < 11) // Java 8, and below, starts with 1, but since we are using Java 11 we can ignore sub values
this.getDebugger().warn("The Java version you are running is outdated for TCPShield and may cause issues. Update to atleast Java 11. Your version: Java %s", jvmVersion);
try {
// Java 11 check/warning (Will eventually force Java 16 without warning)
String[] versionParts = jvmVersion.split("\\.");
int baseVersion = Integer.parseInt(versionParts[0]);
if (baseVersion < 11) // Java 8, and below, starts with 1, but since we are using Java 11 we can ignore sub values
this.getDebugger().warn("The Java version you are running is outdated for TCPShield and may cause issues. Update to atleast Java 11. Your version: Java %s", jvmVersion);
} catch (Throwable t) {
this.getDebugger().error("Failed to check java version for string: " + jvmVersion);
}
}
}
@@ -34,7 +34,6 @@ public class BukkitConfig extends ConfigProvider {
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
@@ -67,11 +66,13 @@ public class BukkitConfig extends ConfigProvider {
try {
loadedConfiguration = YamlConfiguration.loadConfiguration(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode", "enable-geyser-support", "prefer-protocollib");
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
this.preferProtocolLib = loadedConfiguration.getBoolean("prefer-protocollib");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
@@ -1,13 +1,16 @@
package net.tcpshield.tcpshield.bukkit;
import java.util.logging.Logger;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bukkit.paper.BukkitPaper;
import net.tcpshield.tcpshield.bukkit.protocollib.BukkitProtocolLib;
import net.tcpshield.tcpshield.bukkit.provider.BukkitImplProvider;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
/**
@@ -21,7 +24,6 @@ public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
private BukkitImplProvider bukkitImpl;
@Override
public void onEnable() {
try {
@@ -29,23 +31,45 @@ public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
debugger = Debugger.createDebugger(this);
packetHandler = new TCPShieldPacketHandler(this);
if (BukkitImplProvider.hasPaperEvent())
bukkitImpl = new BukkitPaper(this);
else {
if (getServer().getPluginManager().getPlugin("ProtocolLib") == null) {
getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
// check force plib option -> paper -> plib -> error
if (this.configProvider.preferProtocolLib() && getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
try {
String[] protocolLibVersion = getServer().getPluginManager().getPlugin("ProtocolLib").getDescription().getVersion().split("-")[0].split("\\.");
int major = Integer.parseInt(protocolLibVersion[0]);
int minor = Integer.parseInt(protocolLibVersion[1]);
int patch = Integer.parseInt(protocolLibVersion[2]);
String minecraftVersion = Bukkit.getBukkitVersion().split("-")[0];
if (BukkitImplProvider.hasPaperEvent() && major <= 5 && minor <= 2 && patch <= 1 && (minecraftVersion.equals("1.20.5") || minecraftVersion.equals("1.20.6"))) {
getLogger().severe("TCPShield is incompatible with ProtocolLib <= 5.2.1 on Paper 1.20.5/1.20.6 due to lack of support from ProtocolLib. Reverting to default Paper handler to prevent issues. This error can be avoided by disabling 'prefer-protocollib' in the config.");
bukkitImpl = new BukkitPaper(this);
} else {
bukkitImpl = new BukkitProtocolLib(this);
}
} catch (Exception t) {
getLogger().warning("Failed to check Paper or ProtocolLib version. This is not a critical error unless you are running Paper 1.20.5/1.20.6 with ProtocolLib version 5.2.1 or below.");
getDebugger().exception(t);
bukkitImpl = new BukkitProtocolLib(this);
}
} else if (BukkitImplProvider.hasPaperEvent()) {
bukkitImpl = new BukkitPaper(this);
} else if (getServer().getPluginManager().getPlugin("ProtocolLib") != null) {
bukkitImpl = new BukkitProtocolLib(this);
} else {
getLogger().severe("TCPShield not loading because ProtocolLib is not installed. Either use Paper to enable native compatibility or install ProtocolLib.");
return;
}
bukkitImpl.load();
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception e) {
throw new InitializationException(e);
}
}
@@ -68,4 +92,9 @@ public class TCPShieldBukkit extends JavaPlugin implements TCPShieldPlugin {
return configProvider;
}
@Override
public Logger getLogger() {
return super.getLogger();
}
}
@@ -13,7 +13,6 @@ public class BukkitPaper extends BukkitImplProvider {
super(bukkitPlugin);
}
@Override
public void load() {
PaperHandshakeHandler packetHandler = new PaperHandshakeHandler(this);
@@ -19,7 +19,6 @@ public class PaperHandshakeHandler implements Listener {
this.bukkitProvider = bukkitProvider;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
PaperPacket packet = new PaperPacket(e);
@@ -36,6 +35,8 @@ public class PaperHandshakeHandler implements Listener {
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy())
e.setCancelled(true);
}
}
@@ -15,7 +15,6 @@ public class PaperPacket implements PacketProvider {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getPayloadString() {
return handshakeEvent.getOriginalHandshake();
@@ -18,7 +18,6 @@ public class PaperPlayer implements PlayerProvider {
this.handshakeEvent = handshakeEvent;
}
/**
* Trys to grab the UUID of the handshake
* @return If found, the corrosponding uuid, if not, unknown
@@ -58,7 +57,7 @@ public class PaperPlayer implements PlayerProvider {
@Override
public void disconnect() {
handshakeEvent.setCancelled(false); // Caused issues with newer versions of Paper (Thanks https://github.com/realDragonium)
handshakeEvent.setFailMessage("Connection failed. Please try again or contract an administrator.");
handshakeEvent.setFailMessage("Connection failed. Please try again or contact an administrator.");
handshakeEvent.setFailed(true);
}
@@ -13,7 +13,6 @@ public class BukkitProtocolLib extends BukkitImplProvider {
super(bukkitPlugin);
}
@Override
public void load() {
ProtocolLibHandshakeHandler packetHandler = new ProtocolLibHandshakeHandler(this);
@@ -19,7 +19,6 @@ public class ProtocolLibHandshakeHandler extends PacketAdapter {
this.bukkitProvider = bukkitProvider;
}
@Override
public void onPacketReceiving(PacketEvent e) {
ProtocolLibPacket packet = new ProtocolLibPacket(e.getPacket());
@@ -17,7 +17,6 @@ public class ProtocolLibPacket implements PacketProvider {
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getPayloadString() {
return rawPayload;
@@ -1,7 +1,7 @@
package net.tcpshield.tcpshield.bukkit.protocollib.handler;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
@@ -34,7 +34,6 @@ public class ProtocolLibPlayer implements PlayerProvider {
}
}
private final Player player;
private String ip;
@@ -72,13 +71,12 @@ public class ProtocolLibPlayer implements PlayerProvider {
try {
this.ip = ip.getAddress().getHostAddress();
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtil.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtil.getObjectInPrivateField(injector, "networkManager");
Injector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object networkManager = ReflectionUtil.getObjectInPrivateField(ignored, "networkManager");
ReflectionUtil.setFinalField(networkManager, ReflectionUtil.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtil.getObjectInPrivateField(injector, "originalChannel");
Object channel = ReflectionUtil.getObjectInPrivateField(ignored, "channel");
ReflectionUtil.setFinalField(channel, ReflectionUtil.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
@@ -37,7 +37,6 @@ public class BungeeConfig extends ConfigProvider {
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
@@ -76,6 +75,7 @@ public class BungeeConfig extends ConfigProvider {
this.onlyProxy = loadedConfiguration.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedConfiguration.getString("timestamp-validation");
this.doDebug = loadedConfiguration.getBoolean("debug-mode");
this.geyser = loadedConfiguration.getBoolean("enable-geyser-support");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
@@ -6,6 +6,7 @@ import net.md_5.bungee.api.plugin.PluginManager;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.bungee.handler.BungeeHandshakeHandler;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
@@ -19,7 +20,6 @@ public class TCPShieldBungee extends Plugin implements TCPShieldPlugin, Listener
private TCPShieldPacketHandler packetHandler;
private Debugger debugger;
@Override
public void onEnable() {
try {
@@ -30,13 +30,14 @@ public class TCPShieldBungee extends Plugin implements TCPShieldPlugin, Listener
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakeHandler(this));
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception e) {
throw new InitializationException(e);
}
}
/*
* The provider's base methods
*/
@@ -35,6 +35,8 @@ public class BungeeHandshakeHandler implements Listener {
BungeePacket packet = new BungeePacket(e.getHandshake(), e.getConnection());
BungeePlayer player = new BungeePlayer(e.getConnection());
this.plugin.getDebugger().warn("BungeeCord: Raw player hostname: " + packet.getPayloadString());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
@@ -1,11 +1,9 @@
package net.tcpshield.tcpshield.bungee.handler;
import io.netty.channel.AbstractChannel;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
@@ -28,6 +26,7 @@ public class BungeePlayer implements PlayerProvider {
/**
* Trys to grab the UUID of the handshake
*
* @return If found, the corrosponding uuid, if not, unknown
*/
@Override
@@ -64,8 +63,12 @@ public class BungeePlayer implements PlayerProvider {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(AbstractChannel.class, "localAddress"), ip);
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(channel.getClass(), "remoteAddress"), ip);
try {
ReflectionUtil.setFinalField(channel, ReflectionUtil.getPrivateField(channel.getClass(), "localAddress"), ip);
} catch (Throwable t) {
// ChannelWrapper doesn't have a localAddress
}
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
@@ -0,0 +1,49 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import org.geysermc.floodgate.api.InstanceHolder;
import java.util.Arrays;
public class GeyserHandshakeHandler {
private final TCPShieldPlugin plugin;
public GeyserHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
public void init() {
InstanceHolder.getHandshakeHandlers().addHandshakeHandler(data -> {
if (data.getIp() == null) {
// not bedrock
this.plugin.getDebugger().warn("Connection with no bedrock data joined and ignored: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
return;
}
this.plugin.getDebugger().warn("Bedrock connection joined and validated: username = %s, hostname = %s", data.getCorrectUsername(), data.getHostname());
String oldPayload = data.getHostname();
if (oldPayload.contains("///")) {
oldPayload = oldPayload.split("///")[0];
}
String hostname = oldPayload;
String realIp = data.getIp();
String timestamp = "0";
String signature = GeyserUtils.SESSION_SECRET;
// so floodgate likes to append "\0Floodgate\0" or something stupid to the hostname
// which just completely breaks this, so we have to pass these in a different
// order to stop bungeecord detecting it as FML data, thanks floodgate.
String newHostname = realIp + ":0///" + signature + "///" + timestamp + "///" + hostname;
if (hostname.contains("\0")) { // this is usually fine because FML, providing the hostname is first
this.plugin.getDebugger().warn("Hostname contains null byte: " + Arrays.toString(hostname.toCharArray()));
}
this.plugin.getDebugger().warn("Setting hostname to %s - %s", newHostname, Arrays.toString(newHostname.toCharArray()));
data.setHostname(newHostname);
});
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.geyser;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import java.util.UUID;
public class GeyserUtils {
public static final String SESSION_SECRET = UUID.randomUUID().toString();
public static boolean GEYSER_SUPPORT_ENABLED = false;
public static void initGeyser(TCPShieldPlugin plugin, ConfigProvider config) {
if (!(GEYSER_SUPPORT_ENABLED = useGeyser(config))) {
return;
}
GeyserHandshakeHandler geyserHandler = new GeyserHandshakeHandler(plugin);
geyserHandler.init();
}
private static boolean useGeyser(ConfigProvider config) {
if (!config.isGeyser()) return false;
try {
Class.forName("org.geysermc.floodgate.api.InstanceHolder");
return true;
} catch (ClassNotFoundException e) {
return false;
}
}
}
@@ -18,13 +18,25 @@ public abstract class ConfigProvider {
protected boolean onlyProxy = true;
protected String timestampValidationMode = "htpdate";
protected boolean doDebug = true; // Fail-safe default set to true
protected boolean geyser = false;
protected boolean velocityHandlePreLoginEvent = true;
// spigot/paper only option
protected boolean preferProtocolLib;
protected File dataFolder;
protected File configFile;
public boolean isOnlyProxy() {
return onlyProxy;
return this.onlyProxy;
}
public boolean isGeyser() {
return this.geyser;
}
public boolean handlePreLoginEvent() {
return this.velocityHandlePreLoginEvent;
}
public String getTimestampValidationMode() {
@@ -32,7 +44,11 @@ public abstract class ConfigProvider {
}
public boolean doDebug() {
return doDebug;
return this.doDebug;
}
public boolean preferProtocolLib() {
return this.preferProtocolLib;
}
public File getDataFolder() {
@@ -53,7 +69,6 @@ public abstract class ConfigProvider {
return maxTimestampDifference;
}
/*
* Required methods
*/
@@ -0,0 +1,26 @@
package net.tcpshield.tcpshield.util.exception.phase;
import net.tcpshield.tcpshield.util.exception.TCPShieldException;
public class InvalidSecretException extends TCPShieldException {
public InvalidSecretException(Throwable throwable) {
super("An invalid secret was provided during the geyser handshake process", throwable);
}
public InvalidSecretException(String message) {
super(message);
}
public InvalidSecretException(String message, Throwable throwable) {
super(message, throwable);
}
public InvalidSecretException() {
super();
}
}
@@ -34,25 +34,33 @@ public abstract class CIDRMatcher {
String[] split = cidrMatchString.split("/");
String parsedIPAddress;
if (split.length != 0) {
if (split.length > 1) {
parsedIPAddress = split[0];
this.maskBits = Integer.parseInt(split[1]);
this.simpleCIDR = maskBits == 32;
if (maskBits < 0)
throw new CIDRException("Invalid mask: " + split[1]);
} else {
parsedIPAddress = cidrMatchString;
this.maskBits = -1;
this.simpleCIDR = true;
}
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
if (parsedIPAddress.isEmpty())
throw new CIDRException("Empty CIDR entry");
try {
cidrAddress = InetAddress.getByName(parsedIPAddress);
} catch (UnknownHostException e) {
throw new CIDRException(e);
}
int addressBits = cidrAddress.getAddress().length * 8;
if (maskBits > addressBits)
throw new CIDRException("Invalid mask: " + maskBits + " is longer than the " + addressBits + " bit address " + parsedIPAddress);
this.simpleCIDR = maskBits == -1 || maskBits == addressBits;
this.maskBytes = simpleCIDR ? -1 : maskBits / 8;
}
/**
@@ -7,6 +7,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.net.InetAddress;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* A CIDR validator for TCPShield
@@ -18,7 +19,7 @@ public class CIDRValidator {
private final File ipWhitelistFolder;
private final List<CIDRMatcher> cidrMatchers;
private final Set<String> cache = new HashSet<>(); // Max potential size is equilivent to the amount of whitelisted IP's
private final Set<String> cache = ConcurrentHashMap.newKeySet(); // Max potential size is equilivent to the amount of whitelisted IP's
public CIDRValidator(TCPShieldPlugin plugin) throws CIDRException {
this.plugin = plugin;
@@ -7,6 +7,7 @@ import net.tcpshield.tcpshield.util.validation.timestamp.TimestampValidator;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.text.ParseException;
@@ -24,6 +25,8 @@ import java.util.stream.Collectors;
*/
public class HTPDateTimestampValidator extends TimestampValidator {
private static final int TIMEOUT = 10000; // In milliseconds
private volatile long htpDateOffset = 0;
public HTPDateTimestampValidator(TCPShieldPlugin plugin) {
@@ -40,20 +43,23 @@ public class HTPDateTimestampValidator extends TimestampValidator {
private void updateHTPDateOffset() throws IOException {
Socket socket = new Socket("google.com", 80);
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress("google.com", 80), TIMEOUT);
socket.setSoTimeout(TIMEOUT);
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());
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
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());
List<String> response = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8))
.lines()
.collect(Collectors.toList());
Date serverDate = parseDate(response);
Date serverDate = parseDate(response);
htpDateOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
htpDateOffset = Math.round((serverDate.getTime() - readTime) / 1000.0) * 1000; // the HTTP protocol only returns time in seconds; round offset
}
}
private Date parseDate(List<String> response) {
@@ -8,6 +8,7 @@ import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import net.tcpshield.tcpshield.TCPShieldPacketHandler;
import net.tcpshield.tcpshield.TCPShieldPlugin;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.provider.ConfigProvider;
import net.tcpshield.tcpshield.util.Debugger;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
@@ -41,7 +42,6 @@ public class TCPShieldVelocity implements TCPShieldPlugin {
this.dataFolder = dataFolder;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
try {
@@ -51,13 +51,14 @@ public class TCPShieldVelocity implements TCPShieldPlugin {
server.getEventManager().register(this, new VelocityHandshakeHandler(this));
GeyserUtils.initGeyser(this, configProvider);
initialization();
} catch (Exception exception) {
throw new InitializationException(exception);
}
}
/*
* The provider's base methods
*/
@@ -33,7 +33,6 @@ public class VelocityConfig extends ConfigProvider {
}
}
@Override
protected void checkNodes(String... nodes) {
for (String node : nodes) {
@@ -66,11 +65,13 @@ public class VelocityConfig extends ConfigProvider {
try {
loadedToml = new Toml().read(configFile);
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode");
checkNodes("only-allow-proxy-connections", "timestamp-validation", "debug-mode", "enable-geyser-support", "pre-login-event");
this.onlyProxy = loadedToml.getBoolean("only-allow-proxy-connections");
this.timestampValidationMode = loadedToml.getString("timestamp-validation");
this.doDebug = loadedToml.getBoolean("debug-mode");
this.geyser = loadedToml.getBoolean("enable-geyser-support");
this.velocityHandlePreLoginEvent = loadedToml.getBoolean("pre-login-event");
} catch (Exception e) {
throw new ConfigLoadException(e);
}
@@ -16,39 +16,63 @@ public class VelocityHandshakeHandler {
private final TCPShieldPlugin plugin;
private static Class<?> CONNECTED_PLAYER_CONNECTION_CLASS;
static {
try {
CONNECTED_PLAYER_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.ConnectedPlayer");
} catch (Exception e) {
// ignore for old velocity versions
}
}
public VelocityHandshakeHandler(TCPShieldPlugin plugin) {
this.plugin = plugin;
}
// Turns out this event sometimes passes erroneous hostnames
// which have the null bytes terminated as FML data which causes
// issues with the verification process.
@Subscribe(order = PostOrder.FIRST)
public void onPreLogin(PreLoginEvent e) {
if (!this.plugin.getConfigProvider().handlePreLoginEvent()) {
return;
}
InboundConnection connection = e.getConnection();
handleEvent(connection);
handleEvent(connection, "onPreLogin");
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
handleEvent(connection, "onHandshake");
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
if (connection.getClass() == CONNECTED_PLAYER_CONNECTION_CLASS) {
// new ServerData (0x42) packet on connect, we don't care about it
return;
}
handleEvent(connection, "onProxyPing");
}
private void handleEvent(InboundConnection connection) {
private void handleEvent(InboundConnection connection, String debugSource) {
VelocityPlayer player = new VelocityPlayer(connection);
if (player.isLegacy()) {
if (player.getConnectionType() == VelocityPlayer.ConnectionType.LEGACY) {
player.disconnect();
return;
}
VelocityPacket packet = new VelocityPacket(connection);
this.plugin.getDebugger().warn("Velocity: " + debugSource + " Raw player hostname: " + packet.getPayloadString());
try {
plugin.getPacketHandler().handleHandshake(packet, player);
} catch (HandshakeException exception) {
@@ -22,16 +22,36 @@ public class VelocityPacket implements PacketProvider {
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "handshake");
HOSTNAME_FIELD = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
Field hostnameField;
try {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.HandshakePacket"), "serverAddress");
} catch (Exception e) {
hostnameField = ReflectionUtil.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
}
HOSTNAME_FIELD = hostnameField;
CLEANED_ADDRESS_FIELD = ReflectionUtil.getPrivateField(inboundConnection, "cleanedAddress");
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
@@ -39,6 +59,15 @@ public class VelocityPacket implements PacketProvider {
private final String rawPayload;
public VelocityPacket(InboundConnection inboundConnection) {
// support new velocity connection type
if (inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
try {
inboundConnection = (InboundConnection) LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(inboundConnection);
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
@@ -4,7 +4,6 @@ import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.provider.PlayerProvider;
import net.tcpshield.tcpshield.util.ReflectionUtil;
import net.tcpshield.tcpshield.util.exception.manipulate.PlayerManipulationException;
import net.tcpshield.tcpshield.util.exception.phase.HandshakeException;
import net.tcpshield.tcpshield.util.exception.phase.InitializationException;
import net.tcpshield.tcpshield.util.exception.phase.ReflectionException;
@@ -26,6 +25,11 @@ public class VelocityPlayer implements PlayerProvider {
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
// new velocity support
private static Class<?> LOGIN_INBOUND_CONNECTION_CLASS;
private static Field LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
@@ -38,22 +42,39 @@ public class VelocityPlayer implements PlayerProvider {
} catch (Exception e) {
throw new InitializationException(new ReflectionException(e));
}
// LoginInboundConnection support
try {
LOGIN_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.LoginInboundConnection");
LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD = ReflectionUtil.getPrivateField(LOGIN_INBOUND_CONNECTION_CLASS, "delegate");
} catch (Exception e) {
// ignore for old versions of velocity
}
}
private final InboundConnection inboundConnection;
private final boolean legacy;
// private final boolean legacy;
private final ConnectionType connectionType;
private String ip;
public VelocityPlayer(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
// this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS && inboundConnection.getClass() != LOGIN_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
if (this.inboundConnection.getClass() == INITIAL_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.INITIAL_INBOUND;
} else if (this.inboundConnection.getClass() == LOGIN_INBOUND_CONNECTION_CLASS) {
this.connectionType = ConnectionType.LOGIN_INBOUND;
} else {
this.connectionType = ConnectionType.LEGACY;
}
}
/**
* Unsupported with Velocity handshakes
*
* @return unknown
*/
@Override
@@ -63,6 +84,7 @@ public class VelocityPlayer implements PlayerProvider {
/**
* Unsupported with Velocity handshakes
*
* @return unknown
*/
@Override
@@ -75,26 +97,26 @@ public class VelocityPlayer implements PlayerProvider {
return ip;
}
public boolean isLegacy() {
return legacy;
}
@Override
public void setIP(InetSocketAddress ip) throws PlayerManipulationException {
try {
this.ip = ip.getAddress().getHostAddress();
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
Object minecraftConnection = this.getMinecraftConnection();
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new PlayerManipulationException(e);
}
}
// public boolean isLegacy() {
// return legacy;
// }
@Override
public void disconnect() {
try {
Object minecraftConnection = legacy ? LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection) : MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
Object minecraftConnection = this.getMinecraftConnection();
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
@@ -102,4 +124,32 @@ public class VelocityPlayer implements PlayerProvider {
}
}
private Object getMinecraftConnection() {
try {
switch (this.connectionType) {
case LEGACY:
return LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case INITIAL_INBOUND:
return MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
case LOGIN_INBOUND: {
// starts as login_inbound, get delegate initial_inbound
Object initialInboundConnection = LOGIN_INBOUND_CONNECTION_DELEGATE_FIELD.get(this.inboundConnection);
return MINECRAFT_CONNECTION_FIELD.get(initialInboundConnection);
}
}
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return null;
}
public ConnectionType getConnectionType() {
return connectionType;
}
enum ConnectionType {
LOGIN_INBOUND, INITIAL_INBOUND, LEGACY
}
}
+3 -1
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: 2.5.3
version: 2.8.1
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softdepends:
- floodgate
+7 -1
View File
@@ -5,4 +5,10 @@ only-allow-proxy-connections = true
timestamp-validation = 'htpdate'
# Turn on to diagnose connection issues
debug-mode = false
debug-mode = false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support = false
# enable handling handshake on PreLoginEvent
pre-login-event = true
+7 -1
View File
@@ -5,4 +5,10 @@ only-allow-proxy-connections: true
timestamp-validation: 'htpdate'
# Turn on to diagnose connection issues
debug-mode: false
debug-mode: false
# Toggle to enable support for Geyser/Floodgate v2
enable-geyser-support: false
# Spigot/Paper option only, does not affect bungeecord
prefer-protocollib: false
+4 -1
View File
@@ -1,7 +1,10 @@
name: TCPShield
version: 2.5.3
version: 2.8.1
main: net.tcpshield.tcpshield.bukkit.TCPShieldBukkit
softdepend:
- ProtocolLib
- floodgate
author: https://tcpshield.com
description: TCPShield IP parsing capabilities for Velocity
api-version: 1.16
folia-supported: true
+16 -1
View File
@@ -1 +1,16 @@
{"id":"tcpshield","name":"TCPShield","version":"2.5.3","description":"TCPShield IP parsing capabilities for Velocity","authors":["TCPShield"],"dependencies":[],"main":"net.tcpshield.tcpshield.velocity.TCPShieldVelocity"}
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.8.1",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [
{
"id": "floodgate",
"optional": true
}
],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -0,0 +1,103 @@
package net.tcpshield.tcpshield.util.validation.cidr;
import net.tcpshield.tcpshield.util.exception.phase.CIDRException;
import org.junit.jupiter.api.Test;
import java.net.InetAddress;
import java.net.UnknownHostException;
import static org.junit.jupiter.api.Assertions.*;
public class CIDRMatcherTest {
private static boolean matches(String cidr, String address) throws UnknownHostException {
return CIDRMatcher.create(cidr).match(InetAddress.getByName(address));
}
@Test
public void bareAddressMatchesOnlyItself() throws UnknownHostException {
assertTrue(matches("203.0.113.7", "203.0.113.7"));
assertFalse(matches("203.0.113.7", "203.0.113.8"));
}
@Test
public void bareIPv6AddressMatchesOnlyItself() throws UnknownHostException {
assertTrue(matches("2001:db8::1", "2001:db8::1"));
assertFalse(matches("2001:db8::1", "2001:db8::2"));
}
@Test
public void emptyEntryIsRejected() {
assertThrows(CIDRException.class, () -> CIDRMatcher.create(""));
}
@Test
public void ipv6PrefixOf32BitsMatchesTheWholePrefix() throws UnknownHostException {
assertTrue(matches("2001:db8::/32", "2001:db8::"));
assertTrue(matches("2001:db8::/32", "2001:db8:ffff:ffff:ffff:ffff:ffff:ffff"));
assertFalse(matches("2001:db8::/32", "2001:db9::"));
assertFalse(matches("2001:db8::/32", "2002:db8::"));
}
@Test
public void ipv6PrefixOf128BitsMatchesOnlyItself() throws UnknownHostException {
assertTrue(matches("2001:db8::1/128", "2001:db8::1"));
assertFalse(matches("2001:db8::1/128", "2001:db8::2"));
}
@Test
public void ipv4PrefixOf32BitsMatchesOnlyItself() throws UnknownHostException {
assertTrue(matches("203.0.113.7/32", "203.0.113.7"));
assertFalse(matches("203.0.113.7/32", "203.0.113.8"));
}
@Test
public void masksLongerThanTheAddressAreRejected() {
assertThrows(CIDRException.class, () -> CIDRMatcher.create("203.0.113.0/33"));
assertThrows(CIDRException.class, () -> CIDRMatcher.create("2001:db8::/129"));
}
@Test
public void negativeMasksAreRejected() {
assertThrows(CIDRException.class, () -> CIDRMatcher.create("203.0.113.0/-1"));
}
@Test
public void ipv4MaskEdges() throws UnknownHostException {
assertTrue(matches("0.0.0.0/0", "203.0.113.7"));
assertTrue(matches("128.0.0.0/1", "203.0.113.7"));
assertFalse(matches("128.0.0.0/1", "127.255.255.255"));
assertTrue(matches("203.0.0.0/8", "203.255.255.255"));
assertFalse(matches("203.0.0.0/8", "204.0.0.0"));
assertTrue(matches("203.0.0.0/9", "203.127.255.255"));
assertFalse(matches("203.0.0.0/9", "203.128.0.0"));
assertTrue(matches("203.0.112.0/23", "203.0.113.255"));
assertFalse(matches("203.0.112.0/23", "203.0.114.0"));
assertTrue(matches("203.0.113.0/24", "203.0.113.255"));
assertFalse(matches("203.0.113.0/24", "203.0.112.255"));
assertTrue(matches("203.0.113.128/25", "203.0.113.255"));
assertFalse(matches("203.0.113.128/25", "203.0.113.127"));
assertTrue(matches("203.0.113.6/31", "203.0.113.7"));
assertFalse(matches("203.0.113.6/31", "203.0.113.8"));
}
@Test
public void ipv6MaskEdges() throws UnknownHostException {
assertTrue(matches("::/0", "2001:db8::1"));
assertTrue(matches("8000::/1", "ffff::1"));
assertFalse(matches("8000::/1", "7fff::1"));
assertTrue(matches("2001:db8::/48", "2001:db8:0:ffff::1"));
assertFalse(matches("2001:db8::/48", "2001:db8:1::1"));
assertTrue(matches("2001:db8::/49", "2001:db8:0:7fff::1"));
assertFalse(matches("2001:db8::/49", "2001:db8:0:8000::1"));
assertTrue(matches("2001:db8::/127", "2001:db8::1"));
assertFalse(matches("2001:db8::/127", "2001:db8::2"));
}
@Test
public void addressFamiliesNeverMatchEachOther() throws UnknownHostException {
assertFalse(matches("0.0.0.0/0", "2001:db8::1"));
assertFalse(matches("::/0", "203.0.113.7"));
}
}