mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 09:20:15 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ef8abcec46 | ||
|
|
1fb38ec8d4 | ||
|
|
cc5e958e2a | ||
|
|
1437204a15 | ||
|
|
c96d733920 | ||
|
|
c561b3eeaf | ||
|
|
2310864816 | ||
|
|
55030c5073 |
No files matched your search
+3
-4
@@ -21,7 +21,6 @@ versioner {
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenCentral() // Lombok
|
||||
maven(url = "https://jitpack.io/") // simple-yaml
|
||||
maven(url = "https://repo.jonesdev.xyz/releases/") // Bungee & Velocity proxy module
|
||||
maven(url = "https://s01.oss.sonatype.org/content/repositories/snapshots/") // libby
|
||||
}
|
||||
@@ -45,8 +44,7 @@ allprojects {
|
||||
implementation("net.kyori:adventure-nbt:$adventureVersion")
|
||||
|
||||
compileOnly("com.j256.ormlite:ormlite-jdbc:6.1") // ORMLite
|
||||
implementation("xyz.jonesdev:cappuccino:0.1.6-SNAPSHOT") // expiring cache
|
||||
|
||||
compileOnly("com.github.ben-manes.caffeine:caffeine:3.1.8") // caching
|
||||
compileOnly("io.netty:netty-all:4.1.106.Final") // netty
|
||||
|
||||
// Library/dependency loading
|
||||
@@ -73,7 +71,7 @@ tasks {
|
||||
|
||||
shadowJar {
|
||||
// Set the file name of the shadowed jar
|
||||
archiveFileName.set("Sonar.jar")
|
||||
archiveFileName.set("${rootProject.name}.jar")
|
||||
|
||||
// Remove file timestamps
|
||||
isPreserveFileTimestamps = false
|
||||
@@ -98,6 +96,7 @@ tasks {
|
||||
relocate("com.simpleyaml", "xyz.jonesdev.sonar.libs.yaml")
|
||||
relocate("com.google.code.gson", "xyz.jonesdev.sonar.libs.gson")
|
||||
relocate("com.j256.ormlite", "xyz.jonesdev.sonar.libs.ormlite")
|
||||
relocate("com.github.benmanes.caffeine", "xyz.jonesdev.sonar.libs.caffeine")
|
||||
// We want to load our own Gson dynamically
|
||||
exclude("com/google/gson/**")
|
||||
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.16
|
||||
version=2.0.17
|
||||
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
rootProject.name = "sonar"
|
||||
rootProject.name = "Sonar"
|
||||
|
||||
sequenceOf("api", "bukkit", "bungee", "common", "velocity").forEach {
|
||||
val path = "sonar-$it"
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
repositories {
|
||||
maven(url = "https://jitpack.io/") // simple-yaml
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly("com.github.Carleslc.Simple-YAML:Simple-Yaml:1.8.4") {
|
||||
exclude(group = "org.yaml")
|
||||
@@ -6,5 +10,5 @@ dependencies {
|
||||
compileOnly("org.jetbrains:annotations:24.0.1")
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -43,8 +43,8 @@ public final class AttackTracker implements JVMProfiler {
|
||||
public static final class AttackStatistics {
|
||||
private final SystemTimer duration = new SystemTimer();
|
||||
private final SystemTimer timer = new SystemTimer();
|
||||
private int peakJoinsPerSecond;
|
||||
private int peakConnectionsPerSecond;
|
||||
private long peakJoinsPerSecond;
|
||||
private long peakConnectionsPerSecond;
|
||||
private double peakProcessCPUUsage;
|
||||
private long peakProcessMemoryUsage;
|
||||
// Calculate during-attack-statistics using their deltas
|
||||
@@ -52,7 +52,7 @@ public final class AttackTracker implements JVMProfiler {
|
||||
}
|
||||
|
||||
public void checkIfUnderAttack() {
|
||||
final int joinsPerSecond = Counters.LOGINS_PER_SECOND.estimatedSize();
|
||||
final long joinsPerSecond = Counters.LOGINS_PER_SECOND.estimatedSize();
|
||||
final int verifyingPlayers = Sonar.get().getFallback().getConnected().size();
|
||||
final int queuedPlayers = Sonar.get().getFallback().getQueue().getQueuedPlayers().size();
|
||||
final int minPlayers = Sonar.get().getConfig().getMinPlayersForAttack();
|
||||
@@ -80,7 +80,7 @@ public final class AttackTracker implements JVMProfiler {
|
||||
// Update joins per second peak if necessary
|
||||
currentAttack.peakJoinsPerSecond = joinsPerSecond;
|
||||
}
|
||||
final int connectionsPerSecond = Counters.CONNECTIONS_PER_SECOND.estimatedSize();
|
||||
final long connectionsPerSecond = Counters.CONNECTIONS_PER_SECOND.estimatedSize();
|
||||
if (connectionsPerSecond > currentAttack.peakConnectionsPerSecond) {
|
||||
// Update connections per second peak if necessary
|
||||
currentAttack.peakConnectionsPerSecond = connectionsPerSecond;
|
||||
@@ -113,7 +113,7 @@ public final class AttackTracker implements JVMProfiler {
|
||||
final String formattedDuration = String.format("%d minutes, %.0f seconds", minutes, seconds);
|
||||
final String startTimestamp = String.valueOf(currentAttack.duration.getStart() / 1000L);
|
||||
final String endTimestamp = String.valueOf(System.currentTimeMillis() / 1000L);
|
||||
final long blacklisted = Sonar.get().getFallback().getBlacklisted().estimatedSize();
|
||||
final long blacklisted = Sonar.get().getFallback().getBlacklist().estimatedSize();
|
||||
// Calculate during-attack-statistics using their deltas
|
||||
final long totalVerified = Sonar.get().getVerifiedPlayerController().estimatedSize();
|
||||
final long verified = Math.max(totalVerified - currentAttack.successfulVerifications, 0);
|
||||
|
||||
@@ -17,15 +17,17 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.command;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@@ -36,7 +38,9 @@ public interface SonarCommand {
|
||||
|
||||
Map<String, List<String>> ARG_TAB_SUGGESTIONS = new HashMap<>();
|
||||
|
||||
ExpiringCache<Object> DELAY = Cappuccino.buildExpiring(500L);
|
||||
Cache<Audience, Long> COMMAND_DELAY = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(500))
|
||||
.build();
|
||||
|
||||
List<Component> CACHED_HELP_MESSAGE = new ArrayList<>();
|
||||
|
||||
@@ -47,10 +51,11 @@ public interface SonarCommand {
|
||||
source.sendMessage(Sonar.get().getConfig().getNoPermission());
|
||||
return;
|
||||
}
|
||||
// Checking if it contains will only break more since it can throw
|
||||
// a NullPointerException if the cache is being accessed from parallel threads
|
||||
DELAY.cleanUp(); // Clean up the cache
|
||||
final long mapTimestamp = DELAY.asMap().getOrDefault(source, -1L);
|
||||
|
||||
COMMAND_DELAY.cleanUp(); // Clean up the cache
|
||||
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final long mapTimestamp = COMMAND_DELAY.asMap().getOrDefault(source.getAudience(), -1L);
|
||||
|
||||
// There were some exploits with spamming commands in the past.
|
||||
// Spamming should be prevented, especially if some heavy operations are done,
|
||||
@@ -59,7 +64,6 @@ public interface SonarCommand {
|
||||
source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDown());
|
||||
|
||||
// Format delay
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final double left = 0.5D - (timestamp - mapTimestamp) / 1000D;
|
||||
|
||||
source.sendMessage(Sonar.get().getConfig().getCommands().getCommandCoolDownLeft()
|
||||
@@ -67,7 +71,7 @@ public interface SonarCommand {
|
||||
return;
|
||||
}
|
||||
|
||||
DELAY.put(source);
|
||||
COMMAND_DELAY.put(source.getAudience(), timestamp);
|
||||
}
|
||||
|
||||
if (args.length > 0) {
|
||||
|
||||
@@ -170,6 +170,8 @@ public final class SonarConfiguration {
|
||||
private int maxPing;
|
||||
private int readTimeout;
|
||||
private int reconnectDelay;
|
||||
private int blacklistTime;
|
||||
private int blacklistThreshold;
|
||||
private final Collection<Integer> whitelistedProtocols = new HashSet<>(0);
|
||||
private final Collection<Integer> blacklistedProtocols = new HashSet<>(0);
|
||||
|
||||
@@ -318,7 +320,8 @@ public final class SonarConfiguration {
|
||||
minPlayersForAttack = clamp(generalConfig.getInt("min-players-for-attack", 8), 2, 1024);
|
||||
|
||||
generalConfig.getYaml().setComment("min-attack-duration",
|
||||
"Amount of time (in milliseconds) that has to pass in order for an attack to be over");
|
||||
"Amount of time that has to pass in order for an attack to be over"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
minAttackDuration = clamp(generalConfig.getInt("min-attack-duration", 30000), 1000, 900000);
|
||||
|
||||
generalConfig.getYaml().setComment("min-attack-threshold",
|
||||
@@ -327,7 +330,8 @@ public final class SonarConfiguration {
|
||||
minAttackThreshold = clamp(generalConfig.getInt("min-attack-threshold", 2), 0, 20);
|
||||
|
||||
generalConfig.getYaml().setComment("attack-cooldown-delay",
|
||||
"Amount of time (in milliseconds) that has to pass in order for a new attack to be detected");
|
||||
"Amount of time that has to pass in order for a new attack to be detected"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
attackCooldownDelay = clamp(generalConfig.getInt("attack-cooldown-delay", 3000), 100, 30000);
|
||||
|
||||
generalConfig.getYaml().setComment("log-player-addresses",
|
||||
@@ -490,7 +494,8 @@ public final class SonarConfiguration {
|
||||
verification.map.precomputeAmount = generalConfig.getInt("verification.checks.map-captcha.precompute", 1000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.max-duration",
|
||||
"How long should Sonar wait until the player fails the captcha?");
|
||||
"How long should Sonar wait until the player fails the captcha?"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
verification.map.maxDuration = generalConfig.getInt("verification.checks.map-captcha.max-duration", 45000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.map-captcha.max-tries",
|
||||
@@ -526,7 +531,8 @@ public final class SonarConfiguration {
|
||||
verification.maxBrandLength = generalConfig.getInt("verification.checks.max-brand-length", 64);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.max-ping",
|
||||
"Ping (in milliseconds) a player has to have in order to timeout");
|
||||
"Ping a player has to have in order to timeout"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
verification.maxPing = clamp(generalConfig.getInt("verification.checks.max-ping", 10000), 500, 30000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.checks.max-login-packets",
|
||||
@@ -547,13 +553,25 @@ public final class SonarConfiguration {
|
||||
verification.debugXYZPositions = generalConfig.getBoolean("verification.debug-xyz-positions", false);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.read-timeout",
|
||||
"Amount of time that has to pass before a player times out");
|
||||
"Amount of time that has to pass before a player times out"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
verification.readTimeout = clamp(generalConfig.getInt("verification.read-timeout", 3500), 500, 30000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.rejoin-delay",
|
||||
"Minimum number of rejoin delay during verification");
|
||||
"How long should a player wait before reconnecting during verification"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
verification.reconnectDelay = clamp(generalConfig.getInt("verification.rejoin-delay", 8000), 0, 100000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.blacklist-time",
|
||||
"How long should an IP address be denied from logging in when failing the verification too often?"
|
||||
+ LINE_SEPARATOR + "(This value represents the time in milliseconds: 1 second = 1000 milliseconds)");
|
||||
verification.blacklistTime = clamp(generalConfig.getInt("verification.blacklist-time", 600000), 0, 86400000);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.blacklist-threshold",
|
||||
"How often does a player have to fail the verification in order to be blacklisted?"
|
||||
+ LINE_SEPARATOR + "(Set this value to 0 to disable the blacklist entirely)");
|
||||
verification.blacklistThreshold = clamp(generalConfig.getInt("verification.blacklist-threshold", 2), 0, 100);
|
||||
|
||||
generalConfig.getYaml().setComment("verification.whitelisted-protocols",
|
||||
"List of protocol IDs which are not checked by Sonar (verification bypass)"
|
||||
+ LINE_SEPARATOR + "You can find the full list of all protocol IDs here:"
|
||||
@@ -1103,10 +1121,10 @@ public final class SonarConfiguration {
|
||||
verbose.actionBarLayout = formatString(messagesConfig.getString("verbose.layout.normal",
|
||||
String.join(" <dark_aqua>╺ ", Arrays.asList(
|
||||
"%prefix%<gray>CPS <white>%connections-per-second%",
|
||||
"<gray>Logins/s <white>%logins-per-second%",
|
||||
"<gray>Verifying <white>%verifying%"
|
||||
+ " <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s"
|
||||
+ " <green><bold>%animation%<reset>"))));
|
||||
"<gray>Logins/s <white>%logins-per-second%",
|
||||
"<gray>Verifying <white>%verifying%"
|
||||
+ " <dark_aqua>| <green>⬆ <white>%outgoing-traffic%/s <red>⬇ <white>%incoming-traffic%/s"
|
||||
+ " <green><bold>%animation%<reset>"))));
|
||||
messagesConfig.getYaml().setComment("verbose.layout.attack",
|
||||
"Layout for Sonar's actionbar verbose during an attack");
|
||||
verbose.actionBarLayoutDuringAttack = formatString(messagesConfig.getString("verbose.layout.attack",
|
||||
@@ -1150,7 +1168,8 @@ public final class SonarConfiguration {
|
||||
" <dark_aqua>▪ <gray>Queued players: <white>%queued%",
|
||||
"",
|
||||
"<gray>View more information using " +
|
||||
"<white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar verbose</hover></click>" +
|
||||
"<white><click:run_command:'/sonar verbose'><hover:show_text:'(Click to run)'>/sonar " +
|
||||
"verbose</hover></click>" +
|
||||
"<gray> or " +
|
||||
"<white><click:run_command:'/sonar stats'><hover:show_text:'(Click to run)'>/sonar stats</hover></click>" +
|
||||
"<gray>.",
|
||||
|
||||
@@ -17,12 +17,12 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
@@ -31,7 +31,6 @@ import java.net.InetAddress;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Getter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@@ -39,10 +38,14 @@ public final class Fallback {
|
||||
public static final Fallback INSTANCE = new Fallback();
|
||||
private static final @NotNull Sonar SONAR = Objects.requireNonNull(Sonar.get());
|
||||
|
||||
// Map of all connected usernames and their respective IP addresses (used for fast checking)
|
||||
private final Map<String, InetAddress> connected = new ConcurrentHashMap<>(64, 0.75f);
|
||||
// Only block the player for a few minutes to avoid issues
|
||||
private final ExpiringCache<InetAddress> blacklisted = Cappuccino.buildExpiring(
|
||||
10L, TimeUnit.MINUTES, 5000L);
|
||||
// Cache of all blacklisted IP addresses to ensure each entry can expire after the given time
|
||||
@Setter
|
||||
private Cache<InetAddress, Byte> blacklist;
|
||||
@Setter
|
||||
private long blacklistTime;
|
||||
|
||||
private final @NotNull FallbackQueue queue = FallbackQueue.INSTANCE;
|
||||
private final @NotNull FallbackRatelimiter ratelimiter = FallbackRatelimiter.INSTANCE;
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
@Getter
|
||||
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class FallbackQueue {
|
||||
public static final FallbackQueue INSTANCE = new FallbackQueue();
|
||||
static final FallbackQueue INSTANCE = new FallbackQueue();
|
||||
private final Map<InetAddress, Runnable> queuedPlayers = new ConcurrentHashMap<>(64);
|
||||
|
||||
// Runnable task executed for polling the queue
|
||||
|
||||
@@ -17,19 +17,19 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
@Setter
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public final class FallbackRatelimiter {
|
||||
public static final FallbackRatelimiter INSTANCE = new FallbackRatelimiter();
|
||||
@Setter
|
||||
private ExpiringCache<InetAddress> expiringCache;
|
||||
static final FallbackRatelimiter INSTANCE = new FallbackRatelimiter();
|
||||
private Cache<InetAddress, Byte> expiringCache;
|
||||
|
||||
/**
|
||||
* We don't want to clean up the cache in the
|
||||
@@ -37,7 +37,7 @@ public final class FallbackRatelimiter {
|
||||
* it would take up too many resources.
|
||||
*/
|
||||
public void cleanUpCache() {
|
||||
expiringCache.cleanUp(false);
|
||||
expiringCache.cleanUp();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -49,9 +49,9 @@ public final class FallbackRatelimiter {
|
||||
@SuppressWarnings("BooleanMethodIsAlwaysInverted")
|
||||
public boolean attempt(final @NotNull InetAddress inetAddress) {
|
||||
// Cache the IP address if it's not already cached
|
||||
if (!expiringCache.has(inetAddress)) {
|
||||
if (!expiringCache.asMap().containsKey(inetAddress)) {
|
||||
// Cache the IP address
|
||||
expiringCache.put(inetAddress);
|
||||
expiringCache.put(inetAddress, (byte) 0);
|
||||
return true;
|
||||
}
|
||||
// Deny the connection attempt
|
||||
|
||||
@@ -17,15 +17,14 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.fallback;
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelPipeline;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserBlacklistedEvent;
|
||||
import xyz.jonesdev.sonar.api.event.impl.UserVerifyFailedEvent;
|
||||
@@ -33,7 +32,8 @@ import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.api.statistics.Statistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
|
||||
public interface FallbackUser<T> {
|
||||
@NotNull Fallback getFallback();
|
||||
@@ -51,19 +51,6 @@ public interface FallbackUser<T> {
|
||||
|
||||
@NotNull ProtocolVersion getProtocolVersion();
|
||||
|
||||
/**
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
*
|
||||
* @param reason Legacy disconnect message string
|
||||
* @see #disconnect(Component)
|
||||
*/
|
||||
@Deprecated
|
||||
@ApiStatus.ScheduledForRemoval(inVersion = "2.0.17")
|
||||
default void disconnect(final @NotNull String reason) {
|
||||
disconnect(Component.text(reason));
|
||||
}
|
||||
|
||||
/**
|
||||
* Kicks the player from the server with
|
||||
* the given disconnect message.
|
||||
@@ -97,7 +84,39 @@ public interface FallbackUser<T> {
|
||||
}
|
||||
}
|
||||
|
||||
ExpiringCache<String> PREVIOUS_FAILS = Cappuccino.buildExpiring(3L, TimeUnit.MINUTES);
|
||||
Cache<InetAddress, Integer> GLOBAL_FAIL_COUNT = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMinutes(5)) // expire after 5 minutes
|
||||
.build();
|
||||
|
||||
/**
|
||||
* Increments the number of times this user (by IP address) has failed the verification
|
||||
*/
|
||||
default void incrementFails() {
|
||||
final InetAddress inetAddress = Objects.requireNonNull(getInetAddress());
|
||||
final int failCount = getFailedCount();
|
||||
// Make sure to remove the old values from the cache
|
||||
if (failCount > 0) {
|
||||
GLOBAL_FAIL_COUNT.invalidate(inetAddress);
|
||||
}
|
||||
GLOBAL_FAIL_COUNT.put(inetAddress, failCount + 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The number of times this user (by IP address) has failed the verification
|
||||
* @apiNote Returns 0 if no key/value is present in the cache
|
||||
*/
|
||||
default int getFailedCount() {
|
||||
// Make sure to clean up the cache before we try to get cached values
|
||||
GLOBAL_FAIL_COUNT.cleanUp();
|
||||
return GLOBAL_FAIL_COUNT.asMap().getOrDefault(Objects.requireNonNull(getInetAddress()), 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes all previously failed verifications
|
||||
*/
|
||||
default void invalidateFails() {
|
||||
GLOBAL_FAIL_COUNT.invalidate(Objects.requireNonNull(getInetAddress()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnects the player who failed the verification
|
||||
@@ -124,21 +143,32 @@ public interface FallbackUser<T> {
|
||||
// Call the VerifyFailedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserVerifyFailedEvent(this, reason));
|
||||
|
||||
// Make sure old entries are removed
|
||||
PREVIOUS_FAILS.cleanUp();
|
||||
// Use a label, so we can easily add more code beneath this method
|
||||
blacklist: {
|
||||
// Check if the player has too many failed attempts
|
||||
final int blacklistThreshold = Sonar.get().getConfig().getVerification().getBlacklistThreshold();
|
||||
// The user is allowed to disable the blacklist entirely by setting the threshold to 0
|
||||
if (blacklistThreshold <= 0) break blacklist;
|
||||
|
||||
// Check if the player has too many failed attempts
|
||||
if (PREVIOUS_FAILS.has(getInetAddress().toString())) {
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
// Add 1 to the amount of fails since we haven't updated the cached entries yet (for performance)
|
||||
final int failCount = getFailedCount() + 1;
|
||||
// Now we simply need to check if the threshold is reached
|
||||
if (failCount >= blacklistThreshold) {
|
||||
// Call the BotBlacklistedEvent for external API usage
|
||||
Sonar.get().getEventManager().publish(new UserBlacklistedEvent(this));
|
||||
|
||||
getFallback().getBlacklisted().put(getInetAddress());
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
} else {
|
||||
// Cache the InetAddress for 3 minutes
|
||||
PREVIOUS_FAILS.put(getInetAddress().toString());
|
||||
getFallback().getBlacklist().put(getInetAddress(), (byte) 0);
|
||||
getFallback().getLogger().info(Sonar.get().getConfig().getVerification().getBlacklistLog()
|
||||
.replace("%ip%", Sonar.get().getConfig().formatAddress(getInetAddress()))
|
||||
.replace("%protocol%", String.valueOf(getProtocolVersion().getProtocol())));
|
||||
|
||||
// Invalidate the cached entry to ensure memory safety
|
||||
invalidateFails();
|
||||
break blacklist;
|
||||
}
|
||||
|
||||
// Make sure we increment the number of fails
|
||||
incrementFails();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,12 +17,17 @@
|
||||
|
||||
package xyz.jonesdev.sonar.api.statistics;
|
||||
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import com.github.benmanes.caffeine.cache.Cache;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
|
||||
public interface Counters {
|
||||
ExpiringCache<Long> LOGINS_PER_SECOND = Cappuccino.buildExpiring(1L, TimeUnit.SECONDS);
|
||||
ExpiringCache<Long> CONNECTIONS_PER_SECOND = Cappuccino.buildExpiring(1L, TimeUnit.SECONDS);
|
||||
Cache<Long, Byte> LOGINS_PER_SECOND = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofSeconds(1))
|
||||
.build();
|
||||
|
||||
Cache<Long, Byte> CONNECTIONS_PER_SECOND = Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofSeconds(1))
|
||||
.build();
|
||||
}
|
||||
@@ -56,7 +56,7 @@ public final class Notification implements Observable, Counters {
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
|
||||
.replace("%blacklisted%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklisted().estimatedSize()))
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
|
||||
.replace("%total-joins%", DECIMAL_FORMAT.format(Statistics.TOTAL_TRAFFIC.get()))
|
||||
.replace("%logins-per-second%", DECIMAL_FORMAT.format(LOGINS_PER_SECOND.estimatedSize()))
|
||||
.replace("%connections-per-second%", DECIMAL_FORMAT.format(CONNECTIONS_PER_SECOND.estimatedSize()))
|
||||
|
||||
@@ -45,10 +45,10 @@ public final class Verbose implements Observable, JVMProfiler, Counters {
|
||||
// Run action bar verbose
|
||||
public void update() {
|
||||
// Clean up all blacklisted IPs
|
||||
Sonar.get().getFallback().getBlacklisted().cleanUp(false);
|
||||
Sonar.get().getFallback().getBlacklist().cleanUp();
|
||||
// Clean up all counters
|
||||
LOGINS_PER_SECOND.cleanUp(false);
|
||||
CONNECTIONS_PER_SECOND.cleanUp(false);
|
||||
LOGINS_PER_SECOND.cleanUp();
|
||||
CONNECTIONS_PER_SECOND.cleanUp();
|
||||
|
||||
// Don't prepare component if there are no subscribers
|
||||
if (subscribers.isEmpty()) return;
|
||||
@@ -72,7 +72,7 @@ public final class Verbose implements Observable, JVMProfiler, Counters {
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(Sonar.get().getFallback().getConnected().size()))
|
||||
.replace("%blacklisted%",
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklisted().estimatedSize()))
|
||||
DECIMAL_FORMAT.format(Sonar.get().getFallback().getBlacklist().estimatedSize()))
|
||||
.replace("%total-joins%", DECIMAL_FORMAT.format(Statistics.TOTAL_TRAFFIC.get()))
|
||||
.replace("%logins-per-second%", DECIMAL_FORMAT.format(LOGINS_PER_SECOND.estimatedSize()))
|
||||
.replace("%connections-per-second%", DECIMAL_FORMAT.format(CONNECTIONS_PER_SECOND.estimatedSize()))
|
||||
|
||||
@@ -7,7 +7,7 @@ plugins {
|
||||
apply(plugin = "net.minecrell.plugin-yml.bukkit")
|
||||
|
||||
bukkit {
|
||||
name = "Sonar"
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bukkit.SonarBukkitPlugin"
|
||||
authors = listOf("Jones Development", "Sonar Contributors")
|
||||
@@ -44,5 +44,5 @@ dependencies {
|
||||
implementation("com.alessiodp.libby:libby-bukkit:2.0.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -5,7 +5,7 @@ plugins {
|
||||
apply(plugin = "net.minecrell.plugin-yml.bungee")
|
||||
|
||||
bungee {
|
||||
name = "Sonar"
|
||||
name = rootProject.name
|
||||
version = rootProject.version.toString().split("-")[0]
|
||||
main = "xyz.jonesdev.sonar.bungee.SonarBungeePlugin"
|
||||
author = "Jones Development, Sonar Contributors"
|
||||
@@ -29,5 +29,5 @@ dependencies {
|
||||
implementation("com.alessiodp.libby:libby-bungee:2.0.0-SNAPSHOT")
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
+7
-5
@@ -111,7 +111,7 @@ public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
// Use the same channel wrapper as the "real" HandlerBoss
|
||||
channelWrapper = (ChannelWrapper) CHANNEL_WRAPPER_FIELD.get(this);
|
||||
// Increase connections per second for the action bar verbose
|
||||
Counters.CONNECTIONS_PER_SECOND.put(System.nanoTime());
|
||||
Counters.CONNECTIONS_PER_SECOND.put(System.nanoTime(), (byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,7 +143,7 @@ public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
final LoginRequest loginRequest = (LoginRequest) packetWrapper.packet;
|
||||
|
||||
// Increase joins per second for the action bar verbose
|
||||
Counters.LOGINS_PER_SECOND.put(System.nanoTime());
|
||||
Counters.LOGINS_PER_SECOND.put(System.nanoTime(), (byte) 0);
|
||||
|
||||
// Fix login packet spam exploit
|
||||
if (receivedLoginPacket || user != null) {
|
||||
@@ -168,9 +168,11 @@ public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
Statistics.TOTAL_TRAFFIC.increment();
|
||||
|
||||
// Use the ChannelWrapper's remote address to automatically handle proxy protocol
|
||||
final InetAddress inetAddress = ((InetSocketAddress) channelWrapper.getRemoteAddress()).getAddress();
|
||||
final InetSocketAddress socketAddress = (InetSocketAddress) channelWrapper.getRemoteAddress();
|
||||
final InetAddress inetAddress = socketAddress.getAddress();
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklisted().has(inetAddress)) {
|
||||
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
closeWith(getKickPacket(Sonar.get().getConfig().getVerification().getBlacklisted()));
|
||||
return;
|
||||
}
|
||||
@@ -182,7 +184,7 @@ public final class FallbackHandlerBoss extends HandlerBoss {
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections
|
||||
if (isGeyserConnection(channel)) {
|
||||
if (isGeyserConnection(channel, socketAddress)) {
|
||||
FALLBACK.getLogger().info("Skipping Geyser player: {}{}",
|
||||
loginRequest.getData(), Sonar.get().getConfig().formatAddress(inetAddress));
|
||||
super.channelRead(ctx, msg);
|
||||
|
||||
@@ -2,5 +2,5 @@ dependencies {
|
||||
compileOnly(project(":api"))
|
||||
}
|
||||
|
||||
java.sourceCompatibility = JavaVersion.VERSION_1_8
|
||||
java.targetCompatibility = JavaVersion.VERSION_1_8
|
||||
java.sourceCompatibility = JavaVersion.VERSION_11
|
||||
java.targetCompatibility = JavaVersion.VERSION_11
|
||||
@@ -43,6 +43,13 @@ class LibraryLoader {
|
||||
.version("2.10.1")
|
||||
.relocate("com{}google{}code{}gson", "xyz{}jonesdev{}sonar{}libs{}gson")
|
||||
.build(),
|
||||
// Caffeine
|
||||
Library.builder()
|
||||
.groupId("com.github.ben-manes.caffeine")
|
||||
.artifactId("caffeine")
|
||||
.version("3.1.8")
|
||||
.relocate("com{}github{}benmanes{}caffeine", "xyz{}jonesdev{}sonar{}libs{}caffeine")
|
||||
.build(),
|
||||
// ORMLite
|
||||
Library.builder()
|
||||
.groupId("com{}j256{}ormlite")
|
||||
|
||||
@@ -18,19 +18,17 @@
|
||||
package xyz.jonesdev.sonar.common.boot;
|
||||
|
||||
import com.alessiodp.libby.LibraryManager;
|
||||
import com.github.benmanes.caffeine.cache.Caffeine;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.cappuccino.Cappuccino;
|
||||
import xyz.jonesdev.cappuccino.ExpiringCache;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.SonarSupplier;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandRegistry;
|
||||
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
|
||||
import xyz.jonesdev.sonar.api.controller.VerifiedPlayerController;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackRatelimiter;
|
||||
import xyz.jonesdev.sonar.api.timer.SystemTimer;
|
||||
import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
@@ -39,8 +37,7 @@ import xyz.jonesdev.sonar.common.subcommand.SubcommandRegistryHolder;
|
||||
import xyz.jonesdev.sonar.common.update.UpdateChecker;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.time.Duration;
|
||||
|
||||
@Getter
|
||||
@RequiredArgsConstructor
|
||||
@@ -121,17 +118,35 @@ public abstract class SonarBootstrap<T> implements Sonar {
|
||||
// Warn player if they reloaded and changed the database type
|
||||
if (getVerifiedPlayerController() != null
|
||||
&& getVerifiedPlayerController().getCachedDatabaseType() != getConfig().getDatabase().getType()) {
|
||||
Sonar.get().getLogger().warn("Reloading the server after changing the database type"
|
||||
getLogger().warn("Reloading the server after changing the database type"
|
||||
+ " is generally not recommended as it can sometimes cause data loss.");
|
||||
}
|
||||
|
||||
// Prepare cached packets
|
||||
FallbackPreparer.prepare();
|
||||
|
||||
// Update ratelimiter
|
||||
final ExpiringCache<InetAddress> expiringCache = Cappuccino.buildExpiring(
|
||||
getConfig().getVerification().getReconnectDelay(), TimeUnit.MILLISECONDS, 250L);
|
||||
FallbackRatelimiter.INSTANCE.setExpiringCache(expiringCache);
|
||||
// Update ratelimiter cache
|
||||
getFallback().getRatelimiter().setExpiringCache(Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(getConfig().getVerification().getReconnectDelay()))
|
||||
.build());
|
||||
|
||||
// Update blacklist cache
|
||||
final long blacklistTime = getConfig().getVerification().getBlacklistTime();
|
||||
final boolean blacklistExists = getFallback().getBlacklist() != null;
|
||||
// Make sure the blacklist is only set when we need it to prevent data loss
|
||||
if (!blacklistExists // Make sure we create a new cache if it doesn't exist yet
|
||||
|| getFallback().getBlacklistTime() != blacklistTime) {
|
||||
// Create new cache with the configured blacklist time
|
||||
getFallback().setBlacklist(Caffeine.newBuilder()
|
||||
.expireAfterWrite(Duration.ofMillis(getConfig().getVerification().getBlacklistTime()))
|
||||
.build());
|
||||
// Store the new blacklist time, so we don't have to reset the blacklist every reload
|
||||
getFallback().setBlacklistTime(blacklistTime);
|
||||
// Warn the user about changing the expiry of the blacklist values
|
||||
if (blacklistExists) {
|
||||
getLogger().warn("The blacklist has been reset as the duration of entries has changed.");
|
||||
}
|
||||
}
|
||||
|
||||
// Reinitialize database controller
|
||||
verifiedPlayerController = new VerifiedPlayerController();
|
||||
|
||||
+7
-7
@@ -54,7 +54,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the IP is not blacklisted already
|
||||
if (SONAR.getFallback().getBlacklisted().has(inetAddress)) {
|
||||
if (SONAR.getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistDuplicate());
|
||||
return;
|
||||
}
|
||||
@@ -66,7 +66,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
}
|
||||
|
||||
// Blacklist the given IP address
|
||||
SONAR.getFallback().getBlacklisted().put(inetAddress);
|
||||
SONAR.getFallback().getBlacklist().put(inetAddress, (byte) 0);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
@@ -85,20 +85,20 @@ public final class BlacklistCommand extends Subcommand {
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Make sure the IP is blacklisted
|
||||
if (!SONAR.getFallback().getBlacklisted().has(inetAddress)) {
|
||||
if (!SONAR.getFallback().getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistNotFound());
|
||||
return;
|
||||
}
|
||||
|
||||
// Invalidate the cache entry of the blacklisted IP address
|
||||
SONAR.getFallback().getBlacklisted().invalidate(inetAddress);
|
||||
SONAR.getFallback().getBlacklist().invalidate(inetAddress);
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistRemove()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final int blacklisted = SONAR.getFallback().getBlacklisted().estimatedSize();
|
||||
final long blacklisted = SONAR.getFallback().getBlacklist().estimatedSize();
|
||||
|
||||
if (blacklisted == 0) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistEmpty());
|
||||
@@ -106,7 +106,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
}
|
||||
|
||||
// Invalidate all cache entries
|
||||
SONAR.getFallback().getBlacklisted().invalidateAll();
|
||||
SONAR.getFallback().getBlacklist().invalidateAll();
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistCleared()
|
||||
.replace("%removed%", Sonar.DECIMAL_FORMAT.format(blacklisted)));
|
||||
break;
|
||||
@@ -114,7 +114,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
|
||||
case "size": {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistSize()
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklisted().estimatedSize())));
|
||||
.replace("%amount%", Sonar.DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklist().estimatedSize())));
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -74,7 +74,7 @@ public final class StatisticsCommand extends Subcommand implements JVMProfiler {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getGeneralStatistics()
|
||||
.replace("%verified%", DECIMAL_FORMAT.format(SONAR.getVerifiedPlayerController().estimatedSize()))
|
||||
.replace("%verifying%", DECIMAL_FORMAT.format(SONAR.getFallback().getConnected().size()))
|
||||
.replace("%blacklisted%", DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklisted().estimatedSize()))
|
||||
.replace("%blacklisted%", DECIMAL_FORMAT.format(SONAR.getFallback().getBlacklist().estimatedSize()))
|
||||
.replace("%queued%", DECIMAL_FORMAT.format(SONAR.getFallback().getQueue().getQueuedPlayers().size()))
|
||||
.replace("%uptime%", String.format("%dd %dh %dm %ds", days, hours, minutes, seconds % 60L))
|
||||
.replace("%total_joins%", DECIMAL_FORMAT.format(Statistics.TOTAL_TRAFFIC.get()))
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package xyz.jonesdev.sonar.common.utility.geyser;
|
||||
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@@ -28,17 +29,16 @@ import java.net.InetSocketAddress;
|
||||
*/
|
||||
@UtilityClass
|
||||
public class GeyserUtil {
|
||||
// https://github.com/GeyserMC/Floodgate/blob/master/core/src/main/java/org/geysermc/floodgate/module/CommonModule.java#L206
|
||||
private final AttributeKey<Object> PLAYER_ATTRIBUTE = AttributeKey.valueOf("floodgate-player");
|
||||
|
||||
/**
|
||||
* @param channel Channel of the player
|
||||
* @return Whether the player is on GeyserMC or not
|
||||
*/
|
||||
public boolean isGeyserConnection(final @NotNull Channel channel) {
|
||||
// https://discord.com/channels/613163671870242838/613170125696270357/1168599123889504266
|
||||
// Every floodgate player has a channel attribute called "floodgate-player"
|
||||
// However, this is not safe to use as even Java players sometimes have this attribute.
|
||||
// Instead, we should check if the port is 0, since TCP didn't resolve a port (UDP)
|
||||
// Thanks to @dbruni on Discord who reported it in ticket-0072
|
||||
return ((InetSocketAddress) channel.remoteAddress()).getPort() == 0;
|
||||
public boolean isGeyserConnection(final @NotNull Channel channel,
|
||||
final @NotNull InetSocketAddress originalAddress) {
|
||||
return originalAddress.getPort() == 0 // check for floodgate on the server
|
||||
|| channel.attr(PLAYER_ATTRIBUTE).get() != null; // check for standalone Geyser proxy connection
|
||||
}
|
||||
}
|
||||
+7
-5
@@ -55,6 +55,7 @@ import java.lang.invoke.MethodHandle;
|
||||
import java.lang.invoke.MethodHandles;
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -97,13 +98,13 @@ public final class FallbackListener {
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void handle(final @NotNull ConnectionHandshakeEvent event) throws Throwable {
|
||||
// Increase connections per second for the action bar verbose
|
||||
Counters.CONNECTIONS_PER_SECOND.put(System.nanoTime());
|
||||
Counters.CONNECTIONS_PER_SECOND.put(System.nanoTime(), (byte) 0);
|
||||
}
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void handle(final @NotNull PreLoginEvent event) throws Throwable {
|
||||
// Increase joins per second for the action bar verbose
|
||||
Counters.LOGINS_PER_SECOND.put(System.nanoTime());
|
||||
Counters.LOGINS_PER_SECOND.put(System.nanoTime(), (byte) 0);
|
||||
|
||||
final LoginInboundConnection inboundConnection = (LoginInboundConnection) event.getConnection();
|
||||
val initialConnection = (InitialInboundConnection) INITIAL_CONNECTION.invokeExact(inboundConnection);
|
||||
@@ -121,7 +122,8 @@ public final class FallbackListener {
|
||||
final ChannelPipeline pipeline = channel.pipeline();
|
||||
TrafficChannelHooker.hook(pipeline, MINECRAFT_DECODER, MINECRAFT_ENCODER);
|
||||
|
||||
final InetAddress inetAddress = event.getConnection().getRemoteAddress().getAddress();
|
||||
final InetSocketAddress socketAddress = event.getConnection().getRemoteAddress();
|
||||
final InetAddress inetAddress = socketAddress.getAddress();
|
||||
|
||||
// Increase total traffic statistic
|
||||
Statistics.TOTAL_TRAFFIC.increment();
|
||||
@@ -130,7 +132,7 @@ public final class FallbackListener {
|
||||
val activeSessionHandler = mcConnection.getActiveSessionHandler();
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklisted().has(inetAddress)) {
|
||||
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
// Mark the connection as dead to avoid unnecessary console logs
|
||||
markConnectionAsDead(activeSessionHandler);
|
||||
mcConnection.closeWith(DisconnectPacket.create(
|
||||
@@ -143,7 +145,7 @@ public final class FallbackListener {
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) return;
|
||||
|
||||
// Completely skip Geyser connections
|
||||
if (isGeyserConnection(channel)) {
|
||||
if (isGeyserConnection(channel, socketAddress)) {
|
||||
FALLBACK.getLogger().info("Skipping Geyser player: {}{}",
|
||||
event.getUsername(), Sonar.get().getConfig().formatAddress(inetAddress));
|
||||
return;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"id": "Geyser-Velocity",
|
||||
"id": "geyser",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
@@ -18,11 +18,11 @@
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"id": "Protocolize",
|
||||
"id": "protocolize",
|
||||
"optional": true
|
||||
},
|
||||
{
|
||||
"id": "ViaVersion",
|
||||
"id": "viaversion",
|
||||
"optional": true
|
||||
}
|
||||
],
|
||||
|
||||
Reference in new issue
Block a user