104 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
Ryan De Lap d24a6a1a99 Merge pull request #6 from Fuzzlemann/pr
Major rewrite of TCPShield
2020-06-14 02:05:06 -05:00
Fuzzlemann c3b5d209ae Fixes a bug where the config generation on BungeeCord failed. 2020-06-13 00:58:13 +02:00
Fuzzlemann 2ca823411f Fix for the Bungee timeout issue many are facing.
This simple but mandatory commit prevents that the hostname of the server is resolved to its IP which can cause massive network latencies.
2020-06-11 02:25:34 +02:00
Fuzzlemann 197f908fc5 Fixes wrongly named packets 2020-05-28 16:57:23 +02:00
Fuzzlemann a784224592 Updates README.md 2020-05-27 17:00:13 +02:00
Fuzzlemann 57f3e37ff7 Revert "Update README.md"
This reverts commit 3dc3fcad
2020-05-27 16:40:48 +02:00
Fuzzlemann f4b56671b9 Changes server to google.com 2020-05-24 02:18:29 +02:00
Fuzzlemann ed0670c5c5 Implements automatic synchronization of time over HTTP 2020-05-24 00:34:34 +02:00
Fuzzlemann 801fca080a Adds an option for turning on / off timestamp validation in case someone does not have control over the host machine time 2020-05-23 22:35:21 +02:00
Fuzzlemann 00b79770bd Fixes mistakenly named bungeecord implementation of IConfig 2020-05-15 23:03:03 +02:00
Fuzzlemann 9aef0402f5 Adds testing and fixes some issues which were noticed while creating the tests, including:
- correct exception handling when verifying signatures
- also searching private fields when searching fields by class
- making modifier field accessible
2020-05-15 22:52:13 +02:00
Fuzzlemann 1b76e0e6bf Adds support for Velocity 2020-05-15 19:43:11 +02:00
Fuzzlemann 43bbf589de Fix config creation error with BungeeCord 2020-05-15 19:25:47 +02:00
Fuzzlemann 12b8f8da99 removes forgotten debug messages 2020-05-15 18:34:09 +02:00
Fuzzlemann 9cd06036bb Adds native paper support (does not require the installation of ProtocolLib) 2020-05-15 18:32:32 +02:00
Fuzzlemann d9e2e34196 Merge remote-tracking branch 'fork/master' 2020-05-13 00:56:51 +02:00
Fuzzlemann f6bf0346ff -> changes class names related to abstraction 2020-05-13 00:56:37 +02:00
Paul Zhang 3dc3fcad05 Update README.md 2020-05-13 00:05:04 +02:00
Fuzzlemann 60ae269f33 Major code restructuring in order to fulfill quality requirements
-> moves to Gradle
  -> major speedup of build times
  -> automatic adjustment of version in bungee.yml and plugin.yml based on version given in the gradle build file
-> implements abstraction layers
  -> unification of Bungee & Bukkit logic
   -> helps keeping code up-to-date and similar (e.g. timestamp checks were disabled on bukkit but enabled on bungee)
   -> removes boilerplate code which is vulnerable for bugs
-> adds own and specific exceptions
-> improves/changes debug/log messages
-> i.g. code improvements / code style changes
-> some performance improvements
2020-05-12 23:59:46 +02:00
Steven Ferguson b8921033d1 Update version number 2020-05-12 03:59:21 -06:00
Steven Ferguson 92c27ed407 Update version number 2020-05-12 03:58:46 -06:00
72 changed files with 2367 additions and 616 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.
+27 -6
View File
@@ -1,13 +1,34 @@
# RealIP
Bungee/Spigot plugin which parses client IP addresses passed from the TCPShield network.
# TCPShield
TCPShield is the plugin for the same named DDoS mitigation service [TCPShield](https://tcpshield.com).
This plugin is responsible for validating clients join via the TCPShield network.
It also parses passed IP addresses so the server is aware of the real player IP address.
### Compatibility
TCPShield is compatible with Spigot / CraftBukkit, BungeeCord, Velocity and Fabric.
When using Spigot / CraftBukkit, [ProtocolLib](https://github.com/aadnk/ProtocolLib) needs to be installed.
This does not apply when using Paper 1.16 build #503 or higher.
### Setup
Setting up the plugin is easy as pie. Please follow [these](https://docs.tcpshield.com/config/tcpshield-plugin) guidelines.
### Compiling
To compile this project, install maven and run the following command:
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
```
mvn clean install
gradle build
```
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
### Support
Please contact [@RyanDeLap](https://t.me/RyanDeLap) on Telegram for all support related to this product.
See [Contact](https://docs.tcpshield.com/about-us)
Before contact, ensure you have tested with no Bungee Plugins installed. Plugins like BungeePluginManager will break this plugin!
### Contributors
These wonderful contributors have helped TCPShield make this plugin better!
* [Paul Zhang](https://github.com/paulzhng)
* [Draylar](https://github.com/Draylar)
* [RyanDeLap](https://github.com/RyanDeLap)
+170
View File
@@ -0,0 +1,170 @@
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import org.yaml.snakeyaml.DumperOptions
import org.yaml.snakeyaml.Yaml
import java.nio.charset.StandardCharsets
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath group: 'org.yaml', name: 'snakeyaml', version: '1.26'
classpath group: 'com.google.code.gson', name: 'gson', version: '2.8.6'
}
}
plugins {
id 'java'
id 'idea'
id 'fabric-loom' version '0.6-SNAPSHOT'
}
repositories {
maven {
url = 'https://repo1.maven.org/maven2/'
}
maven {
url = 'https://hub.spigotmc.org/nexus/content/repositories/snapshots/'
}
maven {
url = 'https://repo.dmulloy2.net/nexus/repository/public/'
}
maven {
url = 'https://oss.sonatype.org/content/repositories/snapshots'
}
maven {
url = 'https://papermc.io/repo/repository/maven-public/'
}
maven {
url = 'https://repo.velocitypowered.com/snapshots/'
}
maven {
url = 'https://maven.fabricmc.net/'
}
flatDir {
dirs '/libs'
}
}
dependencies {
// Bukkit
compileOnly group: 'org.spigotmc', name: 'spigot-api', version: '1.11-R0.1-SNAPSHOT'
compileOnly group: 'com.comphenix.protocol', name: 'ProtocolLib', version: '4.4.0'
// Paper
compileOnly group: 'com.destroystokyo.paper', name: 'paper-api', version: '1.16.5-R0.1-20210227.144841-85'
// BungeeCord
compileOnly group: 'net.md-5', name: 'bungeecord-api', version: '1.14-SNAPSHOT'
// Velocity
compileOnly group: 'com.velocitypowered', name: 'velocity-api', version: '1.0.0-SNAPSHOT'
// Fabric
minecraft "com.mojang:minecraft:1.16.5"
mappings "net.fabricmc:yarn:1.16.5+build.5:v2"
modImplementation group: 'net.fabricmc', name: 'fabric-loader', version: '0.11.2'
// Testing
testImplementation group: 'org.junit.jupiter', name: 'junit-jupiter-api', version: '5.7.0-M1'
testRuntimeOnly group: 'org.junit.jupiter', name: 'junit-jupiter-engine', version: '5.7.0-M1'
compileOnly 'org.geyser:floodgate-spigot:2.0'
}
test {
useJUnitPlatform()
}
configurations {
testImplementation.extendsFrom compileOnly
}
version = '2.5'
group = 'net.tcpshield.tcpshield'
archivesBaseName = 'TCPShield'
sourceSets {
main {
java {
srcDirs('src/main/java')
}
resources {
srcDirs('src/main/resources')
}
}
test {
java {
srcDirs('src/test/java')
}
resources {
srcDirs('src/test/resources')
}
}
}
idea {
module {
downloadJavadoc = true
downloadSources = true
}
}
compileJava {
sourceCompatibility = targetCompatibility = '1.8'
options.encoding = 'UTF-8'
}
task updateVersion {
updateYaml()
updateJson()
}
void updateYaml() {
DumperOptions options = new DumperOptions()
options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK)
options.setSplitLines(false)
options.setPrettyFlow(true)
def yaml = new Yaml(options)
def bukkitYamlFile = new File('src/main/resources/plugin.yml')
def bungeeYamlFile = new File('src/main/resources/bungee.yml')
def files = [bukkitYamlFile, bungeeYamlFile]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
def cfg = yaml.load(inputStream)
cfg.put("version", version)
new FileWriter(file).withCloseable { writer ->
yaml.dump(cfg, writer)
}
}
}
}
void updateJson() {
def gson = new GsonBuilder().setPrettyPrinting().create();
def velocityJson = new File('src/main/resources/velocity-plugin.json')
def files = [velocityJson]
files.each { file ->
file.newInputStream().withCloseable { inputStream ->
new InputStreamReader(inputStream, StandardCharsets.UTF_8).withCloseable { inputStreamReader ->
JsonObject object = JsonParser.parseReader(inputStreamReader).getAsJsonObject()
object.addProperty("version", version.toString())
new FileWriter(file).withCloseable { writer ->
writer.write(gson.toJson(object))
}
}
}
}
}
build.dependsOn updateVersion
Binary file not shown.
-85
View File
@@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.tcpshield.realip</groupId>
<artifactId>TCPShield</artifactId>
<version>1.7-SNAPSHOT</version>
<properties>
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
</repository>
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/nexus/repository/public/</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.11-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.4.0</version>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>jar</type>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.14-SNAPSHOT</version>
<type>javadoc</type>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<finalName>TCPShield</finalName>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
<resource>
<targetPath>.</targetPath>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources/</directory>
<includes>
<include>*.yml</include>
<include>*.key</include>
</includes>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
+10
View File
@@ -0,0 +1,10 @@
pluginManagement {
repositories {
jcenter()
maven {
name = 'Fabric'
url = 'https://maven.fabricmc.net/'
}
gradlePluginPortal()
}
}
@@ -1,178 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class HandshakePacketHandler extends PacketAdapter {
private String properField = null;
private final Logger logger;
private final boolean onlyProxy;
private final boolean debugMode;
public HandshakePacketHandler(Logger logger, boolean onlyProxy, boolean debugMode) {
super(TCPShieldBukkit.getInstance(), new PacketType[]{PacketType.Handshake.Client.SET_PROTOCOL});
this.logger = logger;
this.onlyProxy = onlyProxy;
this.debugMode = debugMode;
}
public void onPacketReceiving(PacketEvent event) {
boolean proxyConnection = false;
String raw = null;
try {
raw = (String) event.getPacket().getStrings().read(0);
String extraData = "";
String[] hostnameSplits = raw.split("\0", 2);
if(hostnameSplits.length > 1) {
extraData = hostnameSplits[1];
}
raw = hostnameSplits[0];
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
Object injector = ReflectionUtils.getPrivateField(ignored.getClass(), ignored, "injector");
Object networkManager = ReflectionUtils.getPrivateField(injector.getClass(), injector, "networkManager");
if (this.properField == null) {
this.properField = ReflectionUtils.getProperField(networkManager.getClass());
}
Channel channel = (Channel) ReflectionUtils.getPrivateField(injector.getClass(), injector, "originalChannel");
InetSocketAddress newRemoteAddress = new InetSocketAddress(host, port);
proxyConnection = true;
try {
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, this.properField == null ? "l" : this.properField, newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
ReflectionUtils.setFinalField(AbstractChannel.class, channel, "remoteAddress", newRemoteAddress);
} catch (Exception e) {
e.printStackTrace();
}
hostname = hostname + extraData;
event.getPacket().getStrings().write(0, hostname);
}
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
if ((this.onlyProxy) && (!proxyConnection)) {
Player player = event.getPlayer();
if (this.debugMode) {
this.logger.warning("Disconnecting " + player.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if (raw != null) {
this.logger.warning(raw);
}
player.kickPlayer("");
}
}
}
private Class getNmsClass(String name) throws ClassNotFoundException {
return Class.forName(getNmsPackage() + "." + name);
}
private String getNmsPackage() {
String obcString = Bukkit.getServer().getClass().getPackage().getName();
String versionString = obcString.substring(obcString.lastIndexOf('.') + 1);
return "net.minecraft.server." + versionString;
}
public static class ReflectionUtils {
private static final Field FIELD_MODIFIERS;
private static Map<Class<?>, Map<String, Field>> cachedFields = new HashMap();
public static void setFinalField(Class objectClass, Object object, String fieldName, Object value)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
FIELD_MODIFIERS.setInt(field, field.getModifiers() & 0xFFFFFFEF);
}
field.set(object, value);
}
public static Object getPrivateField(Class objectClass, Object object, String fieldName)
throws Exception {
Field field = getDeclaredField(objectClass, fieldName);
field.setAccessible(true);
return field.get(object);
}
public static String getProperField(Class objectClass) {
for (Field f : objectClass.getFields()) {
if (f.getType() == SocketAddress.class) {
return f.getName();
}
}
return "N/A";
}
private static Field getDeclaredField(Class<?> clazz, String fieldName) {
if (!cachedFields.containsKey(clazz)) {
cachedFields.put(clazz, new HashMap());
}
Map<String, Field> clazzCache = (Map) cachedFields.get(clazz);
if (clazzCache.containsKey(fieldName)) {
return (Field) clazzCache.get(fieldName);
}
try {
Field field = clazz.getDeclaredField(fieldName);
clazzCache.put(fieldName, field);
return field;
} catch (NoSuchFieldException e) {
clazzCache.put(fieldName, null);
throw new RuntimeException(e);
}
}
static {
Field field = null;
try {
field = getDeclaredField(Field.class, "modifiers");
field.setAccessible(true);
} catch (Exception e) {
e.printStackTrace();
}
FIELD_MODIFIERS = field;
}
}
}
@@ -1,36 +0,0 @@
package net.tcpshield.realip;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.Signature;
import java.security.SignatureException;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class Signing {
private static PublicKey publicKey;
public static void init()
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
byte[] encodedKey = ByteStreams.toByteArray(Signing.class.getResourceAsStream("/signing_pub.key"));
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC");
publicKey = keyFactory.generatePublic(keySpec);
}
public static boolean verify(byte[] data, String signature)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
byte[] decodedSignature = Base64.getDecoder().decode(signature);
Signature sig = Signature.getInstance("SHA512withECDSA");
sig.initVerify(publicKey);
sig.update(data);
return sig.verify(decodedSignature);
}
}
@@ -1,47 +0,0 @@
package net.tcpshield.realip;
import com.comphenix.protocol.ProtocolLibrary;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldBukkit extends JavaPlugin implements Listener {
private static TCPShieldBukkit INSTANCE;
private static Boolean stopping = Boolean.valueOf(false);
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
INSTANCE = this;
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
saveDefaultConfig();
FileConfiguration config = getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProtocolLibrary.getProtocolManager().addPacketListener(new HandshakePacketHandler(getLogger(), this.onlyProxy, this.debugMode));
}
public void onDisable() {
stopping = Boolean.valueOf(true);
INSTANCE = null;
}
public static TCPShieldBukkit getInstance() {
if (stopping.booleanValue()) {
throw new IllegalAccessError("Plugin is disabling!");
}
return INSTANCE;
}
}
@@ -1,254 +0,0 @@
package net.tcpshield.realip;
import io.netty.channel.AbstractChannel;
import io.netty.channel.Channel;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.CopyOption;
import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.logging.Level;
import java.util.logging.Logger;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.BaseComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.md_5.bungee.event.EventHandler;
public class TCPShieldBungee extends Plugin implements Listener {
private boolean onlyProxy;
private boolean debugMode;
public void onEnable() {
saveDefaultConfig();
Configuration config = loadConfig();
try {
Signing.init();
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException e) {
getLogger().severe("Couldn't initialize signing module.");
throw new RuntimeException(e);
}
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.debugMode = config.getBoolean("debug-mode");
ProxyServer proxy = getProxy();
PluginManager pluginManager = proxy.getPluginManager();
pluginManager.registerListener(this, this);
Logger logger = ProxyServer.getInstance().getLogger();
Logger newLogger = new Logger("BungeeCord", null) {
public void log(Level level, String msg, Object param1) {
if ((msg.equals("{0} has connected")) && (param1.getClass().getSimpleName().equals("InitialHandler"))) {
return;
}
super.log(level, msg, param1);
}
};
newLogger.setParent(logger);
try {
set(ProxyServer.getInstance(), "logger", newLogger);
} catch (Exception e) {
e.printStackTrace();
}
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
private void saveDefaultConfig() {
File dataFolder = getDataFolder();
if (!dataFolder.exists()) {
dataFolder.mkdir();
}
File file = getConfigFile();
if (!file.exists()) {
try {
InputStream in = getResourceAsStream("config.yml");
Throwable localThrowable3 = null;
try {
Files.copy(in, file.toPath(), new CopyOption[0]);
} catch (Throwable localThrowable1) {
localThrowable3 = localThrowable1;
throw localThrowable1;
} finally {
if (in != null) {
if (localThrowable3 != null) {
try {
in.close();
} catch (Throwable localThrowable2) {
localThrowable3.addSuppressed(localThrowable2);
}
} else {
in.close();
}
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
private File getConfigFile() {
File dataFolder = getDataFolder();
return new File(dataFolder, "config.yml");
}
@EventHandler(priority = -64)
public void onProxyPingEvent(ProxyPingEvent event) {
if(event.getConnection().isLegacy()) {
try {
Field wrapperField = event.getConnection().getClass().getDeclaredField("ch");
wrapperField.setAccessible(true);
Object wrapper = wrapperField.get(event.getConnection());
wrapperField.setAccessible(false);
Method m = wrapper.getClass().getMethod("close");
m.invoke(wrapper);
} catch(Exception e) {
event.getConnection().disconnect(new BaseComponent[0]);
}
}
}
@EventHandler(priority = -64)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
boolean proxyConnection = false;
Channel channel = null;
try {
Field wrapperField = e.getConnection().getClass().getDeclaredField("ch");
wrapperField.setAccessible(true);
Object wrapper = wrapperField.get(e.getConnection());
wrapperField.setAccessible(false);
Field chField = wrapper.getClass().getDeclaredField("ch");
chField.setAccessible(true);
Channel ch = (Channel) chField.get(wrapper);
chField.setAccessible(false);
channel = ch;
if (e.getHandshake().getHost().contains("//")) {
String raw = e.getHandshake().getHost();
String[] payload = raw.split("///", 3);
if (payload.length >= 3) {
String hostname = payload[0];
String ipData = payload[1];
String[] ts_sig = payload[2].split("///", 2);
if (ts_sig.length >= 2) {
int timestamp = Integer.parseInt(ts_sig[0]);
String signature = ts_sig[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
if (signature.contains("%%%")) {
signature = signature.split("%%%", 2)[0];
}
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
throw new Exception("Couldn't verify signature.");
}
long currentTime = System.currentTimeMillis() / 1000;
if(!(timestamp >= (currentTime - 2) && timestamp <= (currentTime + 2))) {
if(this.debugMode) {
getLogger().warning("Current time: " + currentTime + ", Timestamp Time: " + timestamp);
}
throw new Exception("Invalid signature timestamp, please check system's local clock if error persists.");
}
PendingConnection connection = e.getConnection();
InetSocketAddress sockadd = new InetSocketAddress(host, port);
proxyConnection = true;
try {
set(wrapper.getClass(), wrapper, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "remoteAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
try {
set(AbstractChannel.class, ch, "localAddress", sockadd);
} catch (Exception ex) {
ex.printStackTrace();
}
InetSocketAddress virtualHost = new InetSocketAddress(hostname, e.getHandshake().getPort());
try {
set(connection, "virtualHost", virtualHost);
} catch (Exception ex) {
try {
set(connection, "vHost", virtualHost);
} catch (Exception e2) {
e2.printStackTrace();
}
}
try {
set(e.getHandshake(), "host", hostname);
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
}
} catch (Exception localException) {
localException.printStackTrace();
} finally {
if (this.onlyProxy && !proxyConnection) {
Logger logger = getLogger();
PendingConnection connection = e.getConnection();
if(this.debugMode) {
logger.warning("Disconnecting " + connection.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
}
if (channel != null) {
channel.flush().close();
} else {
connection.disconnect(new BaseComponent[0]);
}
}
}
}
protected static void set(Object main_object, String field, Object value)
throws Exception {
Field f = main_object.getClass().getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
protected static void set(Class<?> c, Object main_object, String field, Object value)
throws Exception {
Field f = c.getDeclaredField(field);
f.setAccessible(true);
f.set(main_object, value);
}
}
@@ -0,0 +1,93 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.InvalidIPException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import net.tcpshield.tcpshield.validation.IPValidation;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.util.logging.Logger;
public class HandshakePacketHandler {
private final Logger logger;
private final IPValidation ipValidation;
private final TCPShieldConfig config;
public HandshakePacketHandler(Logger logger, TCPShieldConfig config) {
try {
this.logger = logger;
this.ipValidation = new IPValidation(config, logger);
this.config = config;
} catch (Exception e) {
throw new TCPShieldInitializationException(e);
}
}
public void onHandshake(IPacket packet, IPlayer player) {
String rawPayload = packet.getRawPayload();
try {
InetAddress inetAddress = InetAddress.getByName(player.getIP());
if (!ipValidation.validateIP(inetAddress)) throw new InvalidIPException();
// raw payload is passed when it's not available -> e.g. Paper server list ping events
if (rawPayload == null) return;
String extraData = null;
// fix for e.g. incoming FML tagged packets
String cleanedPayload;
int nullIndex = rawPayload.indexOf('\0');
if (nullIndex != -1) { // FML tagged
cleanedPayload = rawPayload.substring(0, nullIndex);
extraData = rawPayload.substring(nullIndex);
} else { // standard
cleanedPayload = rawPayload;
}
String[] payload = cleanedPayload.split("///", 4);
if (payload.length < 2) return; // is nonetheless allowed as it passed the IP check
String hostname = payload[0];
String ipData = payload[1];
String[] hostnameParts = ipData.split(":");
String host = hostnameParts[0];
int port = Integer.parseInt(hostnameParts[1]);
InetSocketAddress newIP = new InetSocketAddress(host, port);
player.setIP(newIP);
if (extraData != null) hostname = hostname + extraData;
packet.modifyOriginalPacket(hostname);
} catch (InvalidIPException e) {
handleInvalidIPException(player, rawPayload);
} catch (IPModificationFailureException e) {
this.logger.warning(String.format("%s's IP failed to be modified. Raw payload = \"%s\"", player.getIP(), rawPayload));
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleInvalidIPException(IPlayer player, String rawPayload) {
if (config.isDebug()) {
this.logger.warning(String.format("%s was disconnected because the client failed the ip check and only-allowed-proxy-connections is enabled. Raw payload = \"%s\"", player.getIP(), rawPayload));
}
if (config.isOnlyProxy()) {
player.disconnect();
}
}
public TCPShieldConfig getConfig() {
return config;
}
}
@@ -0,0 +1,107 @@
package net.tcpshield.tcpshield;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.Table;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Arrays;
public class ReflectionUtils {
private static final Table<Class<?>, String, Field> CACHED_FIELDS_BY_NAME = HashBasedTable.create();
private static final Table<Class<?>, Class<?>, Field> CACHED_FIELDS_BY_CLASS = HashBasedTable.create();
private static Field modifiersField;
public static void setFinalField(Object object, String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException {
setFinalField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setFinalField(Object object, Field field, Object value) throws IllegalAccessException {
field.setAccessible(true);
if (Modifier.isFinal(field.getModifiers())) {
if (modifiersField == null) {
try {
modifiersField = getDeclaredField(Field.class, "modifiers");
} catch (NoSuchFieldException e) { // workaround for when searching for the modifiers field on Java 12 or higher
try {
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
getDeclaredFields0.setAccessible(true);
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
modifiersField = Arrays.stream(fields).filter(modifier -> modifier.getName().equals("modifiers")).findFirst().orElseThrow(() -> new TCPShieldInitializationException("Could not find the modifiers field"));
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e2) {
throw new TCPShieldInitializationException(e2);
}
}
modifiersField.setAccessible(true);
}
modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
}
setField(object, field, value);
}
public static void setField(Object object, String fieldName, Object value) throws IllegalAccessException, NoSuchFieldException {
setField(object, getPrivateField(object.getClass(), fieldName), value);
}
public static void setField(Object object, Field field, Object value) throws IllegalAccessException {
field.set(object, value);
}
public static Object getObjectInPrivateField(Object object, String fieldName) throws IllegalAccessException, NoSuchFieldException {
Field field = getPrivateField(object.getClass(), fieldName);
return field.get(object);
}
public static Field getPrivateField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field field = getDeclaredField(clazz, fieldName);
field.setAccessible(true);
return field;
}
public static Field searchFieldByClass(Class<?> clazz, Class<?> searchFor) {
Field cachedField = CACHED_FIELDS_BY_CLASS.get(clazz, searchFor);
if (cachedField != null) return cachedField;
Class<?> currentClass = clazz;
do {
for (Field field : currentClass.getDeclaredFields()) {
if (!searchFor.isAssignableFrom(field.getType())) continue;
CACHED_FIELDS_BY_CLASS.put(clazz, searchFor, field);
return field;
}
currentClass = currentClass.getSuperclass();
} while (currentClass != null);
throw new IllegalArgumentException("no " + searchFor.getName() + " field for clazz = " + clazz.getName() + " found");
}
public static Field getDeclaredField(Class<?> clazz, String fieldName) throws NoSuchFieldException {
Field cachedField = CACHED_FIELDS_BY_NAME.get(clazz, fieldName);
if (cachedField != null) return cachedField;
Field field;
try {
field = clazz.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
Class<?> superclass = clazz.getSuperclass();
if (superclass != null) {
return getDeclaredField(superclass, fieldName);
} else {
throw e;
}
}
CACHED_FIELDS_BY_NAME.put(clazz, fieldName, field);
return field;
}
}
@@ -0,0 +1,9 @@
package net.tcpshield.tcpshield.abstraction;
public interface IPacket {
String getRawPayload();
void modifyOriginalPacket(String hostname) throws Exception;
}
@@ -0,0 +1,15 @@
package net.tcpshield.tcpshield.abstraction;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.net.InetSocketAddress;
public interface IPlayer {
String getIP();
void setIP(InetSocketAddress ip) throws IPModificationFailureException;
void disconnect();
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.abstraction;
import java.io.File;
public abstract class TCPShieldConfig {
protected boolean onlyProxy;
protected File ipWhitelistFolder;
protected boolean debug;
protected boolean geyser;
public boolean isOnlyProxy() {
return this.onlyProxy;
}
public File getIPWhitelistFolder() {
return ipWhitelistFolder;
}
public boolean isDebug() {
return this.debug;
}
public boolean isGeyser() {
return this.geyser;
}
}
@@ -0,0 +1,38 @@
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 {
@Override
public void onEnable() {
if (isPaper()) {
new TCPShieldPaper(this).load();
} else {
if (Bukkit.getPluginManager().getPlugin("ProtocolLib") == null) {
this.getLogger().warning("TCPShield not loading because ProtocolLib is not installed. Either use Paper (1.16 build #503 or higher) to enable native compatibility or install ProtocolLib.");
return;
}
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<?> handshakeEvent = Class.forName("com.destroystokyo.paper.event.player.PlayerHandshakeEvent");
handshakeEvent.getMethod("getOriginalSocketAddressHostname");
return true;
} catch (ClassNotFoundException | NoSuchMethodException e) {
return false;
}
}
}
@@ -0,0 +1,21 @@
package net.tcpshield.tcpshield.bukkit.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
public class BukkitConfigImpl extends TCPShieldConfig {
public BukkitConfigImpl(JavaPlugin javaPlugin) {
javaPlugin.saveDefaultConfig();
FileConfiguration config = javaPlugin.getConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(javaPlugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
}
@@ -0,0 +1,43 @@
package net.tcpshield.tcpshield.bukkit.paper;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import com.destroystokyo.paper.event.server.PaperServerListPingEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperPlayerImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPacketImpl;
import net.tcpshield.tcpshield.bukkit.paper.impl.PaperServerListPingPlayerImpl;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.plugin.java.JavaPlugin;
public class PaperHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public PaperHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onHandshake(PlayerHandshakeEvent e) {
IPacket packet = new PaperPacketImpl(e);
IPlayer player = new PaperPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onServerPing(PaperServerListPingEvent e) {
if (e.getClient().isLegacy()) e.setCancelled(true);
IPacket packet = new PaperServerListPacketImpl(e);
IPlayer player = new PaperServerListPingPlayerImpl(e);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.bukkit.paper;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserHandshakePacketHandler;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import net.tcpshield.tcpshield.velocity.VelocityHandshakePacketHandler;
import net.tcpshield.tcpshield.velocity.impl.VelocityConfigImpl;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldPaper {
private final JavaPlugin plugin;
public TCPShieldPaper(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PaperHandshakePacketHandler packetHandler = new PaperHandshakePacketHandler(handshakePacketHandler);
Bukkit.getPluginManager().registerEvents(packetHandler, plugin);
});
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class PaperPacketImpl implements IPacket {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPacketImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getRawPayload() {
return handshakeEvent.getOriginalHandshake();
}
@Override
public void modifyOriginalPacket(String hostname) {
handshakeEvent.setCancelled(false);
handshakeEvent.setServerHostname(hostname);
}
}
@@ -0,0 +1,33 @@
package net.tcpshield.tcpshield.bukkit.paper.impl;
import com.destroystokyo.paper.event.player.PlayerHandshakeEvent;
import net.kyori.adventure.text.Component;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import java.net.InetSocketAddress;
public class PaperPlayerImpl implements IPlayer {
private final PlayerHandshakeEvent handshakeEvent;
public PaperPlayerImpl(PlayerHandshakeEvent handshakeEvent) {
this.handshakeEvent = handshakeEvent;
}
@Override
public String getIP() {
return handshakeEvent.getOriginalSocketAddressHostname();
}
@Override
public void setIP(InetSocketAddress ip) {
handshakeEvent.setSocketAddressHostname(ip.getAddress().getHostAddress());
}
@Override
public void disconnect() {
handshakeEvent.setCancelled(false);
handshakeEvent.failMessage(Component.text("Connection failed. Please try again or contact an administrator."));
handshakeEvent.setFailed(true);
}
}
@@ -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);
}
}
@@ -0,0 +1,30 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.ListenerPriority;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketEvent;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPacketImpl;
import net.tcpshield.tcpshield.bukkit.protocollib.impl.ProtocolLibPlayerImpl;
import org.bukkit.plugin.java.JavaPlugin;
public class ProtocolLibHandshakePacketHandler extends PacketAdapter {
private final HandshakePacketHandler handshakePacketHandler;
public ProtocolLibHandshakePacketHandler(JavaPlugin plugin, HandshakePacketHandler handshakePacketHandler) {
super(plugin, ListenerPriority.LOWEST, PacketType.Handshake.Client.SET_PROTOCOL);
this.handshakePacketHandler = handshakePacketHandler;
}
@Override
public void onPacketReceiving(PacketEvent e) {
IPacket packet = new ProtocolLibPacketImpl(e.getPacket());
IPlayer player = new ProtocolLibPlayerImpl(e.getPlayer());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,24 @@
package net.tcpshield.tcpshield.bukkit.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bukkit.impl.BukkitConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
import org.bukkit.plugin.java.JavaPlugin;
public class TCPShieldProtocolLib {
private final JavaPlugin plugin;
public TCPShieldProtocolLib(JavaPlugin plugin) {
this.plugin = plugin;
}
public void load() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(plugin.getLogger(), new BukkitConfigImpl(plugin));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
ProtocolLibHandshakePacketHandler packetHandler = new ProtocolLibHandshakePacketHandler(plugin, handshakePacketHandler);
ProtocolLibrary.getProtocolManager().addPacketListener(packetHandler);
});
}
}
@@ -0,0 +1,25 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.events.PacketContainer;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class ProtocolLibPacketImpl implements IPacket {
private final PacketContainer packetContainer;
private final String rawPayload;
public ProtocolLibPacketImpl(PacketContainer packetContainer) {
this.packetContainer = packetContainer;
this.rawPayload = packetContainer.getStrings().read(0);
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) {
packetContainer.getStrings().write(0, hostname);
}
}
@@ -0,0 +1,64 @@
package net.tcpshield.tcpshield.bukkit.protocollib.impl;
import com.comphenix.protocol.injector.server.SocketInjector;
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import org.bukkit.entity.Player;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class ProtocolLibPlayerImpl implements IPlayer {
private static Class<?> abstractChannelClass;
private final Player player;
private String ip;
static {
try {
abstractChannelClass = Class.forName("io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e) {
try {
abstractChannelClass = Class.forName("net.minecraft.util.io.netty.channel.AbstractChannel");
} catch (ClassNotFoundException e2) {
throw new TCPShieldInitializationException(e2);
}
}
}
public ProtocolLibPlayerImpl(Player player) {
this.player = player;
this.ip = player.getAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Object injector = ReflectionUtils.getObjectInPrivateField(ignored, "injector");
Object networkManager = ReflectionUtils.getObjectInPrivateField(injector, "networkManager");
ReflectionUtils.setFinalField(networkManager, ReflectionUtils.searchFieldByClass(networkManager.getClass(), SocketAddress.class), ip);
Object channel = ReflectionUtils.getObjectInPrivateField(injector, "originalChannel");
ReflectionUtils.setFinalField(channel, ReflectionUtils.getDeclaredField(abstractChannelClass, "remoteAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
player.kickPlayer("");
}
}
@@ -0,0 +1,38 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
import net.md_5.bungee.api.event.ProxyPingEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import net.md_5.bungee.event.EventPriority;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.bungee.impl.BungeePacketImpl;
import net.tcpshield.tcpshield.bungee.impl.BungeePlayerImpl;
public class BungeeHandshakePacketHandler implements Listener {
private final HandshakePacketHandler handshakePacketHandler;
public BungeeHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onProxyPingEvent(ProxyPingEvent e) {
PendingConnection connection = e.getConnection();
if (!connection.isLegacy()) return;
connection.disconnect();
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerHandshake(PlayerHandshakeEvent e) {
IPacket packet = new BungeePacketImpl(e.getHandshake(), e.getConnection());
IPlayer player = new BungeePlayerImpl(e.getConnection());
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,21 @@
package net.tcpshield.tcpshield.bungee;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.api.plugin.PluginManager;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.bungee.impl.BungeeConfigImpl;
import net.tcpshield.tcpshield.geyser.GeyserUtils;
public class TCPShieldBungee extends Plugin implements Listener {
@Override
public void onEnable() {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.getLogger(), new BungeeConfigImpl(this));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> {
PluginManager pluginManager = this.getProxy().getPluginManager();
pluginManager.registerListener(this, new BungeeHandshakePacketHandler(handshakePacketHandler));
});
}
}
@@ -0,0 +1,58 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.plugin.Plugin;
import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class BungeeConfigImpl extends TCPShieldConfig {
private final Plugin plugin;
public BungeeConfigImpl(Plugin plugin) {
this.plugin = plugin;
saveDefaultConfig();
Configuration config = loadConfig();
this.onlyProxy = config.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(plugin.getDataFolder(), "ip-whitelist");
this.geyser = config.getBoolean("enable-geyser-compatibility");
this.debug = config.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
File dataFolder = plugin.getDataFolder();
if (!dataFolder.exists()) dataFolder.mkdir();
File file = getConfigFile();
if (file.exists()) return;
try (InputStream in = plugin.getResourceAsStream("config.yml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
File dataFolder = plugin.getDataFolder();
return new File(dataFolder, "config.yml");
}
private Configuration loadConfig() {
try {
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
File configFile = getConfigFile();
return configProvider.load(configFile);
} catch (IOException e) {
throw new RuntimeException("Couldn't load config", e);
}
}
}
@@ -0,0 +1,36 @@
package net.tcpshield.tcpshield.bungee.impl;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.protocol.packet.Handshake;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import java.net.InetSocketAddress;
public class BungeePacketImpl implements IPacket {
private final Handshake handshake;
private final PendingConnection pendingConnection;
public BungeePacketImpl(Handshake handshake, PendingConnection pendingConnection) {
this.handshake = handshake;
this.pendingConnection = pendingConnection;
}
@Override
public String getRawPayload() {
return handshake.getHost();
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
InetSocketAddress virtualHost = InetSocketAddress.createUnresolved(hostname, handshake.getPort());
try {
ReflectionUtils.setFinalField(pendingConnection, "virtualHost", virtualHost);
} catch (Exception ex) {
ReflectionUtils.setFinalField(pendingConnection, "vHost", virtualHost);
}
ReflectionUtils.setField(handshake, "host", hostname);
}
}
@@ -0,0 +1,54 @@
package net.tcpshield.tcpshield.bungee.impl;
import io.netty.channel.AbstractChannel;
import net.md_5.bungee.api.connection.PendingConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import java.lang.reflect.Field;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
public class BungeePlayerImpl implements IPlayer {
private final PendingConnection pendingConnection;
private String ip;
public BungeePlayerImpl(PendingConnection pendingConnection) {
this.pendingConnection = pendingConnection;
this.ip = pendingConnection.getAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object channelWrapper = ReflectionUtils.getObjectInPrivateField(pendingConnection, "ch");
Object channel = ReflectionUtils.getObjectInPrivateField(channelWrapper, "ch");
try {
Field socketAddressField = ReflectionUtils.searchFieldByClass(channelWrapper.getClass(), SocketAddress.class);
ReflectionUtils.setFinalField(channelWrapper, socketAddressField, ip);
} catch (IllegalArgumentException ignored) {
// Some BungeeCord versions, notably those on 1.7 (e.g. zBungeeCord) don't have an SocketAddress field in the ChannelWrapper class
}
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "remoteAddress"), ip);
ReflectionUtils.setFinalField(channel, ReflectionUtils.getPrivateField(AbstractChannel.class, "localAddress"), ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
pendingConnection.disconnect();
}
}
@@ -0,0 +1,8 @@
package net.tcpshield.tcpshield.exception;
public class IPModificationFailureException extends Exception {
public IPModificationFailureException(Throwable cause) {
super(cause);
}
}
@@ -0,0 +1,4 @@
package net.tcpshield.tcpshield.exception;
public class InvalidIPException extends SecurityException {
}
@@ -0,0 +1,16 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldIPListUpdaterException extends RuntimeException {
public TCPShieldIPListUpdaterException(Throwable cause) {
super(cause);
}
public TCPShieldIPListUpdaterException(String message, Throwable cause) {
super(message, cause);
}
public TCPShieldIPListUpdaterException(String message) {
super(message);
}
}
@@ -0,0 +1,12 @@
package net.tcpshield.tcpshield.exception;
public class TCPShieldInitializationException extends RuntimeException {
public TCPShieldInitializationException(Throwable cause) {
super(cause);
}
public TCPShieldInitializationException(String message) {
super(message);
}
}
@@ -0,0 +1,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();
}
}
}
@@ -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;
}
}
@@ -0,0 +1,39 @@
package net.tcpshield.tcpshield.velocity;
import com.google.inject.Inject;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.ProxyServer;
import 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;
@Plugin(id = "tcpshield",
name = "TCPShield",
description = "TCPShield IP parsing capabilities for Velocity"
)
public class TCPShieldVelocity {
private final ProxyServer server;
private final Logger logger;
private final Path dataFolder;
@Inject
public TCPShieldVelocity(ProxyServer server, Logger logger, @DataDirectory Path dataFolder) {
this.server = server;
this.logger = logger;
this.dataFolder = dataFolder;
}
@Subscribe
public void onProxyInitialization(ProxyInitializeEvent e) {
HandshakePacketHandler handshakePacketHandler = new HandshakePacketHandler(this.logger, new VelocityConfigImpl(dataFolder.toFile()));
GeyserUtils.initGeyserOrDefault(handshakePacketHandler, () -> server.getEventManager().register(this, new VelocityHandshakePacketHandler(handshakePacketHandler)));
}
}
@@ -0,0 +1,44 @@
package net.tcpshield.tcpshield.velocity;
import com.velocitypowered.api.event.PostOrder;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.HandshakePacketHandler;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.velocity.impl.VelocityPacketImpl;
import net.tcpshield.tcpshield.velocity.impl.VelocityPlayerImpl;
public class VelocityHandshakePacketHandler {
private final HandshakePacketHandler handshakePacketHandler;
public VelocityHandshakePacketHandler(HandshakePacketHandler handshakePacketHandler) {
this.handshakePacketHandler = handshakePacketHandler;
}
@Subscribe(order = PostOrder.FIRST)
public void onHandshake(ConnectionHandshakeEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
@Subscribe(order = PostOrder.FIRST)
public void onProxyPing(ProxyPingEvent e) {
InboundConnection connection = e.getConnection();
handleEvent(connection);
}
private void handleEvent(InboundConnection connection) {
VelocityPlayerImpl player = new VelocityPlayerImpl(connection);
if (player.isLegacy()) {
player.disconnect();
return;
}
IPacket packet = new VelocityPacketImpl(connection);
handshakePacketHandler.onHandshake(packet, player);
}
}
@@ -0,0 +1,46 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.moandjiezana.toml.Toml;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
public class VelocityConfigImpl extends TCPShieldConfig {
private final File dataFolder;
private final File file;
public VelocityConfigImpl(File dataFolder) {
this.dataFolder = dataFolder;
this.file = new File(this.dataFolder, "config.toml");
saveDefaultConfig();
Toml toml = loadConfig();
this.onlyProxy = toml.getBoolean("only-allow-proxy-connections");
this.ipWhitelistFolder = new File(dataFolder, "ip-whitelist");
this.geyser = toml.getBoolean("enable-geyser-compatibility");
this.debug = toml.getBoolean("debug-mode");
}
private void saveDefaultConfig() {
if (!dataFolder.exists()) dataFolder.mkdir();
if (file.exists()) return;
try (InputStream in = VelocityConfigImpl.class.getResourceAsStream("/config.toml")) {
Files.copy(in, file.toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private File getConfigFile() {
return new File(dataFolder, "config.toml");
}
private Toml loadConfig() {
return new Toml().read(getConfigFile());
}
}
@@ -0,0 +1,72 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPacket;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
public class VelocityPacketImpl implements IPacket {
private static final Field HANDSHAKE_FIELD;
private static final Field HOSTNAME_FIELD;
private static final Field CLEANED_ADDRESS_FIELD;
static {
try {
Class<?> inboundConnection = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
HANDSHAKE_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "handshake");
HOSTNAME_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.protocol.packet.Handshake"), "serverAddress");
CLEANED_ADDRESS_FIELD = ReflectionUtils.getPrivateField(inboundConnection, "cleanedAddress");
} catch (NoSuchFieldException | ClassNotFoundException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final String rawPayload;
public VelocityPacketImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
try {
this.rawPayload = (String) HOSTNAME_FIELD.get(HANDSHAKE_FIELD.get(inboundConnection));
} catch (IllegalAccessException e) {
throw new RuntimeException(e); // pass on
}
}
@Override
public String getRawPayload() {
return rawPayload;
}
@Override
public void modifyOriginalPacket(String hostname) throws Exception {
ReflectionUtils.setFinalField(inboundConnection, CLEANED_ADDRESS_FIELD, cleanAddress(hostname));
Object handshake = HANDSHAKE_FIELD.get(inboundConnection);
HOSTNAME_FIELD.set(handshake, hostname);
}
/**
* Adapted from https://github.com/VelocityPowered/Velocity/blob/17e6944daea8130e03903ccdfbf63f111c573849/proxy/src/main/java/com/velocitypowered/proxy/connection/client/HandshakeSessionHandler.java
*/
private String cleanAddress(String hostname) {
// Clean out any anything after any zero bytes (this includes BungeeCord forwarding and the
// legacy Forge handshake indicator).
String cleaned = hostname;
int zeroIdx = cleaned.indexOf('\0');
if (zeroIdx > -1) {
cleaned = hostname.substring(0, zeroIdx);
}
// If we connect through an SRV record, there will be a period at the end (DNS usually elides
// this ending octet).
if (!cleaned.isEmpty() && cleaned.charAt(cleaned.length() - 1) == '.') {
cleaned = cleaned.substring(0, cleaned.length() - 1);
}
return cleaned;
}
}
@@ -0,0 +1,76 @@
package net.tcpshield.tcpshield.velocity.impl;
import com.velocitypowered.api.proxy.InboundConnection;
import net.tcpshield.tcpshield.ReflectionUtils;
import net.tcpshield.tcpshield.abstraction.IPlayer;
import net.tcpshield.tcpshield.exception.IPModificationFailureException;
import net.tcpshield.tcpshield.exception.TCPShieldInitializationException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.InetSocketAddress;
public class VelocityPlayerImpl implements IPlayer {
private static final Class<?> INITIAL_INBOUND_CONNECTION_CLASS;
private static final Field MINECRAFT_CONNECTION_FIELD;
private static final Field LEGACY_MINECRAFT_CONNECTION_FIELD;
private static final Field REMOTE_ADDRESS_FIELD;
private static final Method CLOSE_CHANNEL_METHOD;
static {
try {
INITIAL_INBOUND_CONNECTION_CLASS = Class.forName("com.velocitypowered.proxy.connection.client.InitialInboundConnection");
MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(INITIAL_INBOUND_CONNECTION_CLASS, "connection");
LEGACY_MINECRAFT_CONNECTION_FIELD = ReflectionUtils.getPrivateField(Class.forName("com.velocitypowered.proxy.connection.client.HandshakeSessionHandler$LegacyInboundConnection"), "connection");
Class<?> minecraftConnection = Class.forName("com.velocitypowered.proxy.connection.MinecraftConnection");
REMOTE_ADDRESS_FIELD = ReflectionUtils.getPrivateField(minecraftConnection, "remoteAddress");
CLOSE_CHANNEL_METHOD = minecraftConnection.getMethod("close");
} catch (NoSuchFieldException | ClassNotFoundException | NoSuchMethodException e) {
throw new TCPShieldInitializationException(e);
}
}
private final InboundConnection inboundConnection;
private final boolean legacy;
private String ip;
public VelocityPlayerImpl(InboundConnection inboundConnection) {
this.inboundConnection = inboundConnection;
this.legacy = inboundConnection.getClass() != INITIAL_INBOUND_CONNECTION_CLASS;
this.ip = inboundConnection.getRemoteAddress().getAddress().getHostAddress();
}
@Override
public String getIP() {
return ip;
}
public boolean isLegacy() {
return legacy;
}
@Override
public void setIP(InetSocketAddress ip) throws IPModificationFailureException {
this.ip = ip.getAddress().getHostAddress();
try {
Object minecraftConnection = MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
REMOTE_ADDRESS_FIELD.set(minecraftConnection, ip);
} catch (Exception e) {
throw new IPModificationFailureException(e);
}
}
@Override
public void disconnect() {
try {
Object minecraftConnection = legacy ? LEGACY_MINECRAFT_CONNECTION_FIELD.get(inboundConnection) : MINECRAFT_CONNECTION_FIELD.get(inboundConnection);
CLOSE_CHANNEL_METHOD.invoke(minecraftConnection);
} catch (Exception e) {
throw new RuntimeException(e); // pass exception on
}
}
}
+5 -3
View File
@@ -1,4 +1,6 @@
name: TCPShield
version: 1.7
main: net.tcpshield.realip.TCPShieldBungee
author: 'https://tcpshield.com'
version: '2.5'
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
author: https://tcpshield.com
softDepends:
- floodgate
+8
View File
@@ -0,0 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections = true
# Only set this to true in case you're using TCPShield's Geyser proxy!
enable-geyser-compatibility = false
# Turn on to diagnose connection issues
debug-mode = false
+3
View File
@@ -1,5 +1,8 @@
# Please keep this as true in while using TCPShield!
only-allow-proxy-connections: true
# 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": "*"
}
}
+7 -4
View File
@@ -1,5 +1,8 @@
name: TCPShield
version: 1.7
main: net.tcpshield.realip.TCPShieldBukkit
depend: ['ProtocolLib']
author: 'https://tcpshield.com'
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
}
}
+16
View File
@@ -0,0 +1,16 @@
{
"id": "tcpshield",
"name": "TCPShield",
"version": "2.5",
"description": "TCPShield IP parsing capabilities for Velocity",
"authors": [
"TCPShield"
],
"dependencies": [
{
"id": "floodgate",
"optional": true
}
],
"main": "net.tcpshield.tcpshield.velocity.TCPShieldVelocity"
}
@@ -0,0 +1,55 @@
package net.tcpshield.tcpshield;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import net.tcpshield.tcpshield.impl.TestConfigImpl;
import net.tcpshield.tcpshield.impl.TestPacketImpl;
import net.tcpshield.tcpshield.impl.TestPlayerImpl;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.util.logging.Logger;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class HandshakePacketHandlerTest {
private final TCPShieldConfig defaultTestConfig = new TestConfigImpl();
private final HandshakePacketHandler standardHandshakePacketHandler = new HandshakePacketHandler(Logger.getLogger("TCPShield"), defaultTestConfig);
private TestPlayerImpl player;
@BeforeEach
public void setUp() {
player = new TestPlayerImpl(TestConstants.VALID_IP);
}
@Test
public void testSuccessfulHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
public void testSuccessfulFMLTaggedHandshake() {
TestPacketImpl packet = new TestPacketImpl(TestConstants.VALID_PAYLOAD_INVALID_TIMESTAMP_FML_TAGGED, TestConstants.STANDARD_HOSTNAME);
standardHandshakePacketHandler.onHandshake(packet, player);
assertEquals(TestConstants.PAYLOAD_FML_HOSTNAME, packet.getHostname());
assertEquals(TestConstants.PAYLOAD_IP, player.getIP());
}
@Test
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());
}
}
@@ -0,0 +1,102 @@
package net.tcpshield.tcpshield;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Field;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class ReflectionUtilsTest {
private static final String MODIFIED = "modified";
private static final String ORIGINAL = "original";
private ReflectionUtilsTestObject testObject;
@BeforeEach
public void resetTestObject() {
testObject = new ReflectionUtilsTestObject(ORIGINAL);
}
@Test
public void testSetPublicField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "publicString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicString());
}
@Test
public void testSetPrivateField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setField(testObject, "privateString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateString());
}
@Test
public void testSetPublicFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "publicFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPublicFinalString());
}
@Test
public void testSetPrivateFinalField() throws NoSuchFieldException, IllegalAccessException {
ReflectionUtils.setFinalField(testObject, "privateFinalString", MODIFIED);
assertEquals(MODIFIED, testObject.getPrivateFinalString());
}
@Test
public void testSearchFieldByClass() {
Field field = ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, ReflectionUtilsTestObject.class);
assertEquals("testObject", field.getName());
}
@Test
public void testSearchFieldByNonExistentClass() {
Assertions.assertThrows(IllegalArgumentException.class, () -> ReflectionUtils.searchFieldByClass(ReflectionUtilsTest.class, byte.class));
}
@Test
public void testGetObjectInPrivateField() throws NoSuchFieldException, IllegalAccessException {
String privateField = (String) ReflectionUtils.getObjectInPrivateField(testObject, "privateString");
assertEquals(ORIGINAL, privateField);
}
public static class ReflectionUtilsTestObject {
public final String publicFinalString;
private final String privateFinalString;
public String publicString;
private String privateString;
public ReflectionUtilsTestObject(String str) {
this.publicFinalString = str;
this.privateFinalString = str;
this.publicString = str;
this.privateString = str;
}
public String getPublicFinalString() {
return publicFinalString;
}
public String getPrivateFinalString() {
return privateFinalString;
}
public String getPublicString() {
return publicString;
}
public void setPublicString(String publicString) {
this.publicString = publicString;
}
public String getPrivateString() {
return privateString;
}
public void setPrivateString(String privateString) {
this.privateString = privateString;
}
}
}
@@ -0,0 +1,17 @@
package net.tcpshield.tcpshield;
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 = "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";
}
@@ -0,0 +1,14 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.TCPShieldConfig;
import java.io.File;
public class TestConfigImpl extends TCPShieldConfig {
public TestConfigImpl() {
this.ipWhitelistFolder = new File("ip-whitelist");
this.debug = true;
this.onlyProxy = true;
}
}
@@ -0,0 +1,28 @@
package net.tcpshield.tcpshield.impl;
import net.tcpshield.tcpshield.abstraction.IPacket;
public class TestPacketImpl implements IPacket {
private final String payload;
private String hostname;
public TestPacketImpl(String payload, String hostname) {
this.payload = payload;
this.hostname = hostname;
}
@Override
public String getRawPayload() {
return payload;
}
@Override
public void modifyOriginalPacket(String hostname) {
this.hostname = hostname;
}
public String getHostname() {
return hostname;
}
}
@@ -0,0 +1,43 @@
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 InetAddress ip;
private boolean connected;
public TestPlayerImpl(String ip) {
this.connected = true;
try {
this.ip = InetAddress.getByName(ip);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public String getIP() {
if (ip == null) return "ip";
return ip.getHostAddress();
}
@Override
public void setIP(InetSocketAddress ip) {
this.ip = ip.getAddress();
}
@Override
public void disconnect() {
connected = false;
}
public boolean isConnected() {
return connected;
}
}