Compare commits
114
Commits
TCPShield-1.14
...
v2.5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
740e648b92 | ||
|
|
dd51276790 | ||
|
|
8f88b928e8 | ||
|
|
5b76160f78 | ||
|
|
d872880ede | ||
|
|
07b80c0cfa | ||
|
|
f567b6add3 | ||
|
|
a1654ea9af | ||
|
|
7b4543e744 | ||
|
|
c9d088c0d0 | ||
|
|
b215435e03 | ||
|
|
10d78caa56 | ||
|
|
4b6aaf573f | ||
|
|
d3dd1d7a20 | ||
|
|
b80369562e | ||
|
|
44715187c4 | ||
|
|
75e30ec6b3 | ||
|
|
f0f1c53ba8 | ||
|
|
5779306940 | ||
|
|
d031708308 | ||
|
|
46f7e9385b | ||
|
|
f100c0578a | ||
|
|
15c0543114 | ||
|
|
a1d6664906 | ||
|
|
22b0b7065d | ||
|
|
e66f4df4ee | ||
|
|
74bc2b069c | ||
|
|
637c64916b | ||
|
|
0a8d6295b2 | ||
|
|
1d7f2c368a | ||
|
|
086bdd99d5 | ||
|
|
d01aa5efc4 | ||
|
|
d67ece7a0d | ||
|
|
615e4b2289 | ||
|
|
ddf722c069 | ||
|
|
1b716d5e18 | ||
|
|
839d625ce9 | ||
|
|
8a01e915b1 | ||
|
|
32d422a952 | ||
|
|
c5cd43f2fa | ||
|
|
d69d4f7ba8 | ||
|
|
52325a8a2c | ||
|
|
b07fe80979 | ||
|
|
c6471d8e15 | ||
|
|
0dd3c25421 | ||
|
|
42530caa57 | ||
|
|
e58319cabe | ||
|
|
1de480d575 | ||
|
|
1959106ac8 | ||
|
|
5750391e43 | ||
|
|
00f50110fe | ||
|
|
e14d937a70 | ||
|
|
3d5dab69bb | ||
|
|
a4302e4de8 | ||
|
|
e859a1c95a | ||
|
|
a1e9b84b47 | ||
|
|
af998585e9 | ||
|
|
62b18b0842 | ||
|
|
8c0d98fda4 | ||
|
|
78d22e627c | ||
|
|
a6dc0865f7 | ||
|
|
7907d8f731 | ||
|
|
32142f9961 | ||
|
|
f8d32f5e96 | ||
|
|
f8da84de07 | ||
|
|
cafc278c32 | ||
|
|
e958718bcb | ||
|
|
031c2ed091 | ||
|
|
faf7568c81 | ||
|
|
812f5c353f | ||
|
|
6216b0c01e | ||
|
|
901ce0cfeb | ||
|
|
a537099297 | ||
|
|
7a14a45338 | ||
|
|
8f35cf9532 | ||
|
|
39a977fa97 | ||
|
|
2178727b21 | ||
|
|
553332c641 | ||
|
|
23c207e3b9 | ||
|
|
bf40833d5d | ||
|
|
818df3cbb1 | ||
|
|
fcc3ef4fef | ||
|
|
d39f520279 | ||
|
|
d24a6a1a99 | ||
|
|
c3b5d209ae | ||
|
|
2ca823411f | ||
|
|
197f908fc5 | ||
|
|
a784224592 | ||
|
|
57f3e37ff7 | ||
|
|
f4b56671b9 | ||
|
|
ed0670c5c5 | ||
|
|
801fca080a | ||
|
|
00b79770bd | ||
|
|
9aef0402f5 | ||
|
|
1b76e0e6bf | ||
|
|
43bbf589de | ||
|
|
12b8f8da99 | ||
|
|
9cd06036bb | ||
|
|
d9e2e34196 | ||
|
|
f6bf0346ff | ||
|
|
3dc3fcad05 | ||
|
|
60ae269f33 | ||
|
|
b8921033d1 | ||
|
|
92c27ed407 | ||
|
|
d4e41d2c28 | ||
|
|
751bcafcc6 | ||
|
|
a641acdfb6 | ||
|
|
c89705c0c8 | ||
|
|
4c8e446600 | ||
|
|
961c6c0e20 | ||
|
|
30140afcf6 | ||
|
|
65020bbcd3 | ||
|
|
7d746b25ff | ||
|
|
4bedf22b59 |
No files matched your search
+12
-3
@@ -1,4 +1,13 @@
|
||||
# Testing
|
||||
ip-whitelist/
|
||||
|
||||
# IntelliJ
|
||||
.idea/
|
||||
*/target/
|
||||
*/*/target/
|
||||
*.iml
|
||||
build/
|
||||
*.iml
|
||||
|
||||
# Gradle
|
||||
.gradle/
|
||||
gradle/
|
||||
gradlew
|
||||
gradlew.bat
|
||||
@@ -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.
|
||||
@@ -1,2 +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
|
||||
In order to compile TCPShield, [install Gradle](https://docs.gradle.org/current/userguide/installation.html) and run the following command in the project folder:
|
||||
```
|
||||
gradle build
|
||||
```
|
||||
|
||||
The dependencies should install themselves automatically. After the build has finished, the compiled jar file can be found under `/build/libs`.
|
||||
|
||||
### Support
|
||||
See [Contact](https://docs.tcpshield.com/about-us)
|
||||
|
||||
### 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
@@ -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.
@@ -1,85 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>com.tcpshield.realip</groupId>
|
||||
<artifactId>RealIP</artifactId>
|
||||
<version>1.4-SNAPSHOT</version>
|
||||
<properties>
|
||||
<project.build.sourceEncoding>ISO-8859-1</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/nexus/repository/public/</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>bungeecord-repo</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.11-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>4.4.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.14-SNAPSHOT</version>
|
||||
<type>jar</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-api</artifactId>
|
||||
<version>1.14-SNAPSHOT</version>
|
||||
<type>javadoc</type>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
<finalName>RealIP</finalName>
|
||||
<sourceDirectory>src/main/java</sourceDirectory>
|
||||
<resources>
|
||||
<resource>
|
||||
<targetPath>.</targetPath>
|
||||
<filtering>true</filtering>
|
||||
<directory>${basedir}/src/main/resources/</directory>
|
||||
<includes>
|
||||
<include>*.yml</include>
|
||||
<include>*.key</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>2.3.1</version>
|
||||
<configuration>
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
</project>
|
||||
@@ -0,0 +1,10 @@
|
||||
pluginManagement {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
name = 'Fabric'
|
||||
url = 'https://maven.fabricmc.net/'
|
||||
}
|
||||
gradlePluginPortal()
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
package net.tcpshield.realip;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.PacketType.Handshake.Client;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.reflect.StructureModifier;
|
||||
import io.netty.channel.AbstractChannel;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class PacketHandler
|
||||
extends PacketAdapter {
|
||||
private String properField = null;
|
||||
private final Logger logger;
|
||||
private final boolean onlyProxy;
|
||||
|
||||
public PacketHandler(Logger logger, boolean onlyProxy) {
|
||||
super(TCPShieldBukkit.getInstance(), new PacketType[]{PacketType.Handshake.Client.SET_PROTOCOL});
|
||||
this.logger = logger;
|
||||
this.onlyProxy = onlyProxy;
|
||||
}
|
||||
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
boolean proxyConnection = false;
|
||||
String raw = null;
|
||||
try {
|
||||
raw = (String) event.getPacket().getStrings().read(0);
|
||||
|
||||
String[] payload = raw.split("///", 3);
|
||||
if (payload.length >= 3) {
|
||||
String hostname = payload[0];
|
||||
String ipData = payload[1];
|
||||
String[] ts_sig = payload[2].split("///", 2);
|
||||
if (ts_sig.length >= 2) {
|
||||
int timestamp = Integer.parseInt(ts_sig[0]);
|
||||
String signature = ts_sig[1];
|
||||
|
||||
String[] hostnameParts = ipData.split(":");
|
||||
String host = hostnameParts[0];
|
||||
int port = Integer.parseInt(hostnameParts[1]);
|
||||
|
||||
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
|
||||
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
|
||||
throw new Exception("Couldn't verify signature.");
|
||||
}
|
||||
try {
|
||||
SocketInjector ignored = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
|
||||
Object injector = ReflectionUtils.getPrivateField(ignored.getClass(), ignored, "injector");
|
||||
Object networkManager = ReflectionUtils.getPrivateField(injector.getClass(), injector, "networkManager");
|
||||
if (this.properField == null) {
|
||||
this.properField = ReflectionUtils.getProperField(networkManager.getClass());
|
||||
}
|
||||
Channel channel = (Channel) ReflectionUtils.getPrivateField(injector.getClass(), injector, "originalChannel");
|
||||
InetSocketAddress newRemoteAddress = new InetSocketAddress(host, port);
|
||||
proxyConnection = true;
|
||||
try {
|
||||
ReflectionUtils.setFinalField(networkManager.getClass(), networkManager, this.properField == null ? "l" : this.properField, newRemoteAddress);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
ReflectionUtils.setFinalField(AbstractChannel.class, channel, "remoteAddress", newRemoteAddress);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
event.getPacket().getStrings().write(0, hostname);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
Player player;
|
||||
ex.printStackTrace();
|
||||
} finally {
|
||||
if ((this.onlyProxy) &&
|
||||
(!proxyConnection)) {
|
||||
Player player = event.getPlayer();
|
||||
this.logger.warning("Disconnecting " + player.getAddress() + " because no proxy info was received and only-allow-proxy-connections is enabled.");
|
||||
if(raw != null) {
|
||||
this.logger.warning(raw);
|
||||
}
|
||||
player.kickPlayer("");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Channel getChannel(Player player) {
|
||||
try {
|
||||
Class<? extends Player> playerClass = player.getClass();
|
||||
Object playerHandle = playerClass.getMethod("getHandle", new Class[0]).invoke(player, new Object[0]);
|
||||
Object playerConnection = getNmsClass("EntityPlayer").getField("playerConnection").get(playerHandle);
|
||||
Object networkManager = getNmsClass("PlayerConnection").getField("networkManager").get(playerConnection);
|
||||
return (Channel) getNmsClass("NetworkManager").getField("channel").get(networkManager);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Couldn't get player channel", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Class getNmsClass(String name)
|
||||
throws ClassNotFoundException {
|
||||
return Class.forName(getNmsPackage() + "." + name);
|
||||
}
|
||||
|
||||
private String getNmsPackage() {
|
||||
String obcString = Bukkit.getServer().getClass().getPackage().getName();
|
||||
String versionString = obcString.substring(obcString.lastIndexOf('.') + 1);
|
||||
return "net.minecraft.server." + versionString;
|
||||
}
|
||||
|
||||
public static class ReflectionUtils {
|
||||
private static final Field FIELD_MODIFIERS;
|
||||
private static Map<Class<?>, Map<String, Field>> cachedFields = new HashMap();
|
||||
|
||||
public static void setFinalField(Class objectClass, Object object, String fieldName, Object value)
|
||||
throws Exception {
|
||||
Field field = getDeclaredField(objectClass, fieldName);
|
||||
field.setAccessible(true);
|
||||
if (Modifier.isFinal(field.getModifiers())) {
|
||||
FIELD_MODIFIERS.setInt(field, field.getModifiers() & 0xFFFFFFEF);
|
||||
}
|
||||
field.set(object, value);
|
||||
}
|
||||
|
||||
public static Object getPrivateField(Class objectClass, Object object, String fieldName)
|
||||
throws Exception {
|
||||
Field field = getDeclaredField(objectClass, fieldName);
|
||||
field.setAccessible(true);
|
||||
return field.get(object);
|
||||
}
|
||||
|
||||
public static String getProperField(Class objectClass) {
|
||||
for (Field f : objectClass.getFields()) {
|
||||
if (f.getType() == SocketAddress.class) {
|
||||
return f.getName();
|
||||
}
|
||||
}
|
||||
return "N/A";
|
||||
}
|
||||
|
||||
private static Field getDeclaredField(Class<?> clazz, String fieldName) {
|
||||
if (!cachedFields.containsKey(clazz)) {
|
||||
cachedFields.put(clazz, new HashMap());
|
||||
}
|
||||
Map<String, Field> clazzCache = (Map) cachedFields.get(clazz);
|
||||
if (clazzCache.containsKey(fieldName)) {
|
||||
return (Field) clazzCache.get(fieldName);
|
||||
}
|
||||
try {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
clazzCache.put(fieldName, field);
|
||||
return field;
|
||||
} catch (NoSuchFieldException e) {
|
||||
clazzCache.put(fieldName, null);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
static {
|
||||
Field field = null;
|
||||
try {
|
||||
field = getDeclaredField(Field.class, "modifiers");
|
||||
field.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
FIELD_MODIFIERS = field;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
package net.tcpshield.realip;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Base64;
|
||||
|
||||
public class Signing {
|
||||
private static PublicKey publicKey;
|
||||
|
||||
public static void init()
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
byte[] encodedKey = ByteStreams.toByteArray(Signing.class.getResourceAsStream("/signing_pub.key"));
|
||||
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encodedKey);
|
||||
|
||||
KeyFactory keyFactory = KeyFactory.getInstance("EC");
|
||||
publicKey = keyFactory.generatePublic(keySpec);
|
||||
}
|
||||
|
||||
public static boolean verify(byte[] data, String signature)
|
||||
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||
byte[] decodedSignature = Base64.getDecoder().decode(signature);
|
||||
Signature sig = Signature.getInstance("SHA512withECDSA");
|
||||
sig.initVerify(publicKey);
|
||||
sig.update(data);
|
||||
return sig.verify(decodedSignature);
|
||||
}
|
||||
}
|
||||
@@ -1,46 +0,0 @@
|
||||
package net.tcpshield.realip;
|
||||
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class TCPShieldBukkit
|
||||
extends JavaPlugin {
|
||||
private static TCPShieldBukkit INSTANCE;
|
||||
private static Boolean stopping = Boolean.valueOf(false);
|
||||
private boolean onlyProxy;
|
||||
|
||||
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");
|
||||
|
||||
ProtocolLibrary.getProtocolManager().addPacketListener(new PacketHandler(getLogger(), this.onlyProxy));
|
||||
}
|
||||
|
||||
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,221 +0,0 @@
|
||||
package net.tcpshield.realip;
|
||||
|
||||
import io.netty.channel.AbstractChannel;
|
||||
import io.netty.channel.Channel;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.api.plugin.PluginManager;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
public class TCPShieldBungee
|
||||
extends Plugin
|
||||
implements Listener {
|
||||
private boolean onlyProxy;
|
||||
|
||||
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");
|
||||
|
||||
ProxyServer proxy = getProxy();
|
||||
PluginManager pluginManager = proxy.getPluginManager();
|
||||
pluginManager.registerListener(this, this);
|
||||
|
||||
Logger logger = ProxyServer.getInstance().getLogger();
|
||||
Logger newLogger = new Logger("BungeeCord", null) {
|
||||
public void log(Level level, String msg, Object param1) {
|
||||
if ((msg.equals("{0} has connected")) && (param1.getClass().getSimpleName().equals("InitialHandler"))) {
|
||||
return;
|
||||
}
|
||||
super.log(level, msg, param1);
|
||||
}
|
||||
};
|
||||
newLogger.setParent(logger);
|
||||
try {
|
||||
set(ProxyServer.getInstance(), "logger", newLogger);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
private Configuration loadConfig() {
|
||||
try {
|
||||
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
File configFile = getConfigFile();
|
||||
return configProvider.load(configFile);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Couldn't load config", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveDefaultConfig() {
|
||||
File dataFolder = getDataFolder();
|
||||
if (!dataFolder.exists()) {
|
||||
dataFolder.mkdir();
|
||||
}
|
||||
File file = getConfigFile();
|
||||
if (!file.exists()) {
|
||||
try {
|
||||
InputStream in = getResourceAsStream("config.yml");
|
||||
Throwable localThrowable3 = null;
|
||||
try {
|
||||
Files.copy(in, file.toPath(), new CopyOption[0]);
|
||||
} catch (Throwable localThrowable1) {
|
||||
localThrowable3 = localThrowable1;
|
||||
throw localThrowable1;
|
||||
} finally {
|
||||
if (in != null) {
|
||||
if (localThrowable3 != null) {
|
||||
try {
|
||||
in.close();
|
||||
} catch (Throwable localThrowable2) {
|
||||
localThrowable3.addSuppressed(localThrowable2);
|
||||
}
|
||||
} else {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getConfigFile() {
|
||||
File dataFolder = getDataFolder();
|
||||
return new File(dataFolder, "config.yml");
|
||||
}
|
||||
|
||||
@EventHandler(priority = -64)
|
||||
public void onPlayerHandshake(PlayerHandshakeEvent e) {
|
||||
boolean proxyConnection = false;
|
||||
Channel channel = null;
|
||||
try {
|
||||
Field wrapperField = e.getConnection().getClass().getDeclaredField("ch");
|
||||
wrapperField.setAccessible(true);
|
||||
Object wrapper = wrapperField.get(e.getConnection());
|
||||
wrapperField.setAccessible(false);
|
||||
|
||||
Field chField = wrapper.getClass().getDeclaredField("ch");
|
||||
chField.setAccessible(true);
|
||||
Channel ch = (Channel) chField.get(wrapper);
|
||||
chField.setAccessible(false);
|
||||
channel = ch;
|
||||
if (e.getHandshake().getHost().contains("//")) {
|
||||
String raw = e.getHandshake().getHost();
|
||||
|
||||
String[] payload = raw.split("///", 3);
|
||||
if (payload.length >= 3) {
|
||||
String hostname = payload[0];
|
||||
String ipData = payload[1];
|
||||
String[] ts_sig = payload[2].split("///", 2);
|
||||
if (ts_sig.length >= 2) {
|
||||
int timestamp = Integer.parseInt(ts_sig[0]);
|
||||
String signature = ts_sig[1];
|
||||
|
||||
String[] hostnameParts = ipData.split(":");
|
||||
String host = hostnameParts[0];
|
||||
int port = Integer.parseInt(hostnameParts[1]);
|
||||
|
||||
String reconstructedPayload = hostname + "///" + host + ":" + port + "///" + timestamp;
|
||||
if (!Signing.verify(reconstructedPayload.getBytes(StandardCharsets.UTF_8), signature)) {
|
||||
throw new Exception("Couldn't verify signature.");
|
||||
}
|
||||
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) {
|
||||
Logger logger;
|
||||
PendingConnection connection;
|
||||
localException.printStackTrace();
|
||||
} finally {
|
||||
if ((this.onlyProxy) &&
|
||||
(!proxyConnection)) {
|
||||
Logger logger = getLogger();
|
||||
PendingConnection connection = e.getConnection();
|
||||
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);
|
||||
}
|
||||
}
|
||||
+23
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
}
|
||||
}
|
||||
+30
@@ -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);
|
||||
});
|
||||
}
|
||||
}
|
||||
+25
@@ -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);
|
||||
}
|
||||
}
|
||||
+64
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
name: TCPShield
|
||||
version: 1.3
|
||||
main: net.tcpshield.realip.TCPShieldBungee
|
||||
author: 'https://tcpshield.com'
|
||||
version: '2.5'
|
||||
main: net.tcpshield.tcpshield.bungee.TCPShieldBungee
|
||||
author: https://tcpshield.com
|
||||
softDepends:
|
||||
- floodgate
|
||||
@@ -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
|
||||
@@ -1 +1,8 @@
|
||||
only-allow-proxy-connections: true
|
||||
# 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
|
||||
@@ -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": "*"
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
name: TCPShield
|
||||
version: 1.3
|
||||
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.
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user