Refactor of checkPlayer logic to fix potential concurrency issues. This was a poor design. Also implemented gradle tooling for debugging velocity plugins

This commit is contained in:
2026-04-30 20:54:37 -04:00
parent 8637cd5e8b
commit bc6828f8af
10 changed files with 143 additions and 82 deletions
+2 -1
View File
@@ -295,4 +295,5 @@ $RECYCLE.BIN/
# End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans # End of https://www.toptal.com/developers/gitignore/api/windows,macos,linux,maven,java,intellij,eclipse,netbeans
/.gradle/ /.gradle/
.grade/** .grade/**
/run/
@@ -18,6 +18,7 @@ package dev.brighten.antivpn.bukkit;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.api.CheckResult;
import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.OfflinePlayer;
import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
@@ -82,28 +83,28 @@ public class BukkitListener extends VPNExecutor implements Listener {
event.getAddress() event.getAddress()
)); ));
player.checkPlayer(result -> { CheckResult result = player.checkPlayer();
if(!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if(!result.resultType().isShouldBlock()) return;
return;
}
event.setResult(PlayerLoginEvent.Result.KICK_BANNED); if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
event.setKickMessage(switch (result.resultType()) { return;
case DENIED_COUNTRY -> StringUtil.varReplace( }
AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
player, event.setResult(PlayerLoginEvent.Result.KICK_BANNED);
result.response() event.setKickMessage(switch (result.resultType()) {
); case DENIED_COUNTRY -> StringUtil.varReplace(
case DENIED_PROXY -> AntiVPN.getInstance().getVpnConfig().getCountryVanillaKickReason(),
StringUtil.varReplace( player,
AntiVPN.getInstance().getVpnConfig().getKickMessage(), result.response()
player, );
result.response() case DENIED_PROXY ->
); StringUtil.varReplace(
default -> "You were kicked by KauriVPN for an unknown reason!"; AntiVPN.getInstance().getVpnConfig().getKickMessage(),
}); player,
result.response()
);
default -> "You were kicked by KauriVPN for an unknown reason!";
}); });
} }
@@ -77,7 +77,6 @@ public class BungeeListener extends VPNExecutor implements Listener {
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
public void onListener(final PreLoginEvent event) { public void onListener(final PreLoginEvent event) {
APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId()) APIPlayer player = AntiVPN.getInstance().getPlayerExecutor().getPlayer(event.getConnection().getUniqueId())
.orElseGet(() -> { .orElseGet(() -> {
UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName()); UUID uuid = MiscUtils.lookupUUID(event.getConnection().getName());
@@ -88,22 +87,22 @@ public class BungeeListener extends VPNExecutor implements Listener {
((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress()); ((InetSocketAddress) event.getConnection().getSocketAddress()).getAddress());
}); });
player.checkPlayer(result -> { CheckResult result = player.checkPlayer();
if (!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if (!result.resultType().isShouldBlock()) return;
return;
}
event.setCancelled(true); if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) { return;
case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() }
.getKickMessage(), player, result.response());
case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() event.setCancelled(true);
.getCountryVanillaKickReason(), player, result.response()); event.setReason(TextComponent.fromLegacy(StringUtil.varReplace(switch (result.resultType()) {
default -> "You were kicked by KauriVPN for an unknown reason!"; case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
}, player, result.response()))); .getKickMessage(), player, result.response());
}); case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getCountryVanillaKickReason(), player, result.response());
default -> "You were kicked by KauriVPN for an unknown reason!";
}, player, result.response())));
} }
@EventHandler(priority = EventPriority.HIGH) @EventHandler(priority = EventPriority.HIGH)
@@ -26,7 +26,6 @@ import lombok.Setter;
import java.net.InetAddress; import java.net.InetAddress;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.logging.Level; import java.util.logging.Level;
@Getter @Getter
@@ -74,7 +73,12 @@ public abstract class APIPlayer {
); );
} }
public void checkPlayer(Consumer<CheckResult> onResult) { /***
* The result is only returned if it is cached. Otherwise, there is an asynchronous call to the API
* and will handle kicking the player if necessary.
* @return CheckResult - The cached result of the check if it exists.
*/
public CheckResult checkPlayer() {
if (hasPermission("antivpn.bypass") //Has bypass permission if (hasPermission("antivpn.bypass") //Has bypass permission
//Is exempt //Is exempt
|| (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid)) || (uuid != null && AntiVPN.getInstance().getExecutor().isWhitelisted(uuid))
@@ -82,8 +86,7 @@ public abstract class APIPlayer {
|| AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32") || AntiVPN.getInstance().getExecutor().isWhitelisted(ip.getHostAddress() + "/32")
|| AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream() || AntiVPN.getInstance().getVpnConfig().getPrefixWhitelists().stream()
.anyMatch(name::startsWith)) { .anyMatch(name::startsWith)) {
onResult.accept(new CheckResult(null, ResultType.WHITELISTED, false)); return new CheckResult(null, ResultType.WHITELISTED, false);
return;
} }
CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress()); CheckResult cachedResult = checkResultCache.getIfPresent(ip.getHostAddress());
@@ -94,8 +97,7 @@ public abstract class APIPlayer {
if(cachedResult.resultType().isShouldBlock()) { if(cachedResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this); AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(cachedResult, this);
} }
onResult.accept(cachedResult); return cachedResult;
return;
} }
} }
@@ -105,7 +107,6 @@ public abstract class APIPlayer {
AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " + AntiVPN.getInstance().getExecutor().log(Level.WARNING, "The API query was not a success! " +
"You may need to upgrade your license on " + "You may need to upgrade your license on " +
"https://funkemunky.cc/shop"); "https://funkemunky.cc/shop");
onResult.accept(new CheckResult(null, ResultType.API_FAILURE, false));
return; return;
} }
// If the countryList() size is zero, no need to check. // If the countryList() size is zero, no need to check.
@@ -137,9 +138,8 @@ public abstract class APIPlayer {
if(checkResult.resultType().isShouldBlock()) { if(checkResult.resultType().isShouldBlock()) {
AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this); AntiVPN.getInstance().getExecutor().handleKickingOfPlayer(checkResult, this);
} }
onResult.accept(checkResult);
AntiVPN.getInstance().checked++; AntiVPN.getInstance().checked++;
}); });
onResult.accept(new CheckResult(null, ResultType.UNKNOWN, false)); return new CheckResult(null, ResultType.UNKNOWN, false);
} }
} }
@@ -16,6 +16,7 @@
package dev.brighten.antivpn.utils; package dev.brighten.antivpn.utils;
import java.io.Serial;
import java.io.Serializable; import java.io.Serializable;
import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT; import static dev.brighten.antivpn.utils.NullnessCasts.uncheckedCastNullableTToT;
@@ -36,7 +37,7 @@ public final class Suppliers {
/** /**
* Returns a supplier which caches the instance retrieved during the first call to {@code get()} * Returns a supplier which caches the instance retrieved during the first call to {@code get()}
* and returns that value on subsequent calls to {@code get()}. See: <a * and returns that value on later calls to {@code get()}. See: <a
* href="http://en.wikipedia.org/wiki/Memoization">memoization</a> * href="http://en.wikipedia.org/wiki/Memoization">memoization</a>
* *
* <p>The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at * <p>The returned supplier is thread-safe. The delegate's {@code get()} method will be invoked at
@@ -44,7 +45,7 @@ public final class Suppliers {
* form does not contain the cached value, which will be recalculated when {@code get()} is called * form does not contain the cached value, which will be recalculated when {@code get()} is called
* on the reserialized instance. * on the reserialized instance.
* *
* <p>When the underlying delegate throws an exception then this memoizing supplier will keep * <p>When the underlying delegate throws an exception then this memorizing supplier will keep
* delegating calls until it returns valid data. * delegating calls until it returns valid data.
* *
* <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is * <p>If {@code delegate} is an instance created by an earlier call to {@code memoize}, it is
@@ -95,6 +96,7 @@ public final class Suppliers {
+ ")"; + ")";
} }
@Serial
private static final long serialVersionUID = 0; private static final long serialVersionUID = 0;
} }
+15
View File
@@ -8,3 +8,18 @@ Just a simple plugin using an incredibly fast and accurate API.
## SpigotMC Page ## SpigotMC Page
Rate and support the project on SpigotMC: https://www.spigotmc.org/resources/kaurivpn-anti-proxy-tor-and-vpn-free-api.93355/ Rate and support the project on SpigotMC: https://www.spigotmc.org/resources/kaurivpn-anti-proxy-tor-and-vpn-free-api.93355/
## Velocity Debugging
Run a generated local Velocity proxy with the AntiVPN Velocity loader installed:
```bash
./gradlew runVelocity
```
Run the proxy suspended for IDE debugger attach on port `5005`:
```bash
./gradlew debugVelocity
```
In IntelliJ IDEA, use the Gradle `debugVelocity` task and attach a remote JVM debugger to `localhost:5005`.
@@ -41,22 +41,22 @@ public class SpongeListener extends VPNExecutor {
event.connection().address().getAddress() event.connection().address().getAddress()
))); )));
player.get().checkPlayer(result -> { CheckResult result = player.get().checkPlayer();
if(!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if(!result.resultType().isShouldBlock()) return;
return;
}
event.setCancelled(true); if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
event.setMessage(Component.text(switch (result.resultType()) { return;
case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() }
.getKickMessage(), player.get(), result.response());
case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig() event.setCancelled(true);
.getCountryVanillaKickReason(), player.get(), result.response()); event.setMessage(Component.text(switch (result.resultType()) {
default -> "You were kicked by KauriVPN for an unknown reason!"; case DENIED_PROXY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
})); .getKickMessage(), player.get(), result.response());
}); case DENIED_COUNTRY -> StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getCountryVanillaKickReason(), player.get(), result.response());
default -> "You were kicked by KauriVPN for an unknown reason!";
}));
} }
@Listener @Listener
+29
View File
@@ -1,5 +1,6 @@
plugins { plugins {
id 'com.gradleup.shadow' id 'com.gradleup.shadow'
id 'xyz.jpenilla.run-velocity'
} }
evaluationDependsOn(':Velocity:VelocityPlugin') evaluationDependsOn(':Velocity:VelocityPlugin')
@@ -25,16 +26,44 @@ shadowJar {
rename { 'antivpn-velocity.jarinjar' } rename { 'antivpn-velocity.jarinjar' }
} }
// Include the shared shaded source jar required by JarInJarClassLoader.
from(project(':Common:Source').tasks.named('shadowJar')) {
rename { 'antivpn-source.jarinjar' }
}
relocate 'org.bstats', 'dev.brighten.antivpn.velocity.org.bstats' relocate 'org.bstats', 'dev.brighten.antivpn.velocity.org.bstats'
relocate 'org.yaml.snakeyaml', 'dev.brighten.antivpn.shaded.org.yaml.snakeyaml' relocate 'org.yaml.snakeyaml', 'dev.brighten.antivpn.shaded.org.yaml.snakeyaml'
} }
tasks.named('shadowJar') { tasks.named('shadowJar') {
dependsOn(':Common:Source:shadowJar')
dependsOn(':Velocity:VelocityPlugin:shadowJar') dependsOn(':Velocity:VelocityPlugin:shadowJar')
} }
tasks.build.dependsOn shadowJar tasks.build.dependsOn shadowJar
tasks.named('runVelocity') {
velocityVersion('3.4.0-SNAPSHOT')
runDirectory.set(rootProject.layout.projectDirectory.dir('run/velocity'))
pluginJars.from(tasks.named('shadowJar'))
dependsOn(tasks.named('shadowJar'))
}
tasks.register('debugVelocity', xyz.jpenilla.runvelocity.task.RunVelocity) {
group = 'Run Velocity'
description = 'Run a local Velocity proxy for plugin debugging on port 5005.'
velocityVersion('3.4.0-SNAPSHOT')
runDirectory.set(rootProject.layout.projectDirectory.dir('run/velocity'))
pluginJars.from(tasks.named('shadowJar'))
dependsOn(tasks.named('shadowJar'))
debugOptions {
enabled = true
port = 5005
server = true
suspend = true
}
}
jar { jar {
archiveClassifier.set('raw') archiveClassifier.set('raw')
} }
@@ -21,6 +21,7 @@ import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.LoginEvent; import com.velocitypowered.api.event.connection.LoginEvent;
import dev.brighten.antivpn.AntiVPN; import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.api.APIPlayer; import dev.brighten.antivpn.api.APIPlayer;
import dev.brighten.antivpn.api.CheckResult;
import dev.brighten.antivpn.api.OfflinePlayer; import dev.brighten.antivpn.api.OfflinePlayer;
import dev.brighten.antivpn.api.VPNExecutor; import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.utils.StringUtil; import dev.brighten.antivpn.utils.StringUtil;
@@ -52,35 +53,35 @@ public class VelocityListener extends VPNExecutor {
event.getPlayer().getRemoteAddress().getAddress() event.getPlayer().getRemoteAddress().getAddress()
)); ));
player.checkPlayer(result -> { CheckResult result = player.checkPlayer();
if(!result.resultType().isShouldBlock()) return;
if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) { if(!result.resultType().isShouldBlock()) return;
return;
}
switch (result.resultType()) { if(!AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied( return;
LegacyComponentSerializer.builder() }
.character('&')
.build().deserialize(AntiVPN.getInstance().getVpnConfig() switch (result.resultType()) {
.getCountryVanillaKickReason() case DENIED_COUNTRY -> event.setResult(ResultedEvent.ComponentResult.denied(
.replace("%player%", event.getPlayer().getUsername()) LegacyComponentSerializer.builder()
.replace("%country%", result.response().getCountryName())
.replace("%code%", result.response().getCountryCode()))));
case DENIED_PROXY -> {
VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername()
+ " joined on a VPN/Proxy (" + result.response().getMethod() + ")");
event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder()
.character('&') .character('&')
.build().deserialize(AntiVPN.getInstance().getVpnConfig() .build().deserialize(AntiVPN.getInstance().getVpnConfig()
.getKickMessage() .getCountryVanillaKickReason()
.replace("%player%", event.getPlayer().getUsername()) .replace("%player%", event.getPlayer().getUsername())
.replace("%country%", result.response().getCountryName()) .replace("%country%", result.response().getCountryName())
.replace("%code%", result.response().getCountryCode())))); .replace("%code%", result.response().getCountryCode()))));
} case DENIED_PROXY -> {
VelocityPlugin.INSTANCE.getLogger().info(event.getPlayer().getUsername()
+ " joined on a VPN/Proxy (" + result.response().getMethod() + ")");
event.setResult(ResultedEvent.ComponentResult.denied(LegacyComponentSerializer.builder()
.character('&')
.build().deserialize(AntiVPN.getInstance().getVpnConfig()
.getKickMessage()
.replace("%player%", event.getPlayer().getUsername())
.replace("%country%", result.response().getCountryName())
.replace("%code%", result.response().getCountryCode()))));
} }
}); }
} }
@Override @Override
+13
View File
@@ -1,6 +1,7 @@
plugins { plugins {
id 'java' id 'java'
id 'com.gradleup.shadow' version '9.4.1' id 'com.gradleup.shadow' version '9.4.1'
id 'xyz.jpenilla.run-velocity' version '3.0.2' apply false
} }
def aggregateTestProjects = [ def aggregateTestProjects = [
@@ -101,3 +102,15 @@ tasks.named('shadowJar') {
} }
tasks.build.dependsOn shadowJar tasks.build.dependsOn shadowJar
tasks.register('runVelocity') {
group = 'run'
description = 'Starts a local Velocity proxy with the AntiVPN Velocity loader installed.'
dependsOn(':Velocity:VelocityLoader:runVelocity')
}
tasks.register('debugVelocity') {
group = 'run'
description = 'Starts a local Velocity proxy suspended for debugger attach on port 5005.'
dependsOn(':Velocity:VelocityLoader:debugVelocity')
}