Start of versioning database functions and handling errors

This commit is contained in:
2025-12-30 07:24:35 -08:00
parent 9ea3141ae7
commit 7913676323
11 changed files with 558 additions and 115 deletions
+6
View File
@@ -173,6 +173,12 @@
<version>3.12.14</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>26.0.2</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>
@@ -5,11 +5,10 @@ import dev.brighten.antivpn.api.VPNConfig;
import dev.brighten.antivpn.api.VPNExecutor;
import dev.brighten.antivpn.command.Command;
import dev.brighten.antivpn.command.impl.AntiVPNCommand;
import dev.brighten.antivpn.database.Database;
import dev.brighten.antivpn.database.mongodb.MongoDatabase;
import dev.brighten.antivpn.database.postgres.PostgresDatabase;
import dev.brighten.antivpn.database.sqllite.LiteDatabase;
import dev.brighten.antivpn.database.sqllite.version.Version;
import dev.brighten.antivpn.database.VPNDatabase;
import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.mongo.MongoVPN;
import dev.brighten.antivpn.database.sql.MySqlVPN;
import dev.brighten.antivpn.depends.LibraryLoader;
import dev.brighten.antivpn.depends.MavenLibrary;
import dev.brighten.antivpn.depends.Relocate;
@@ -30,31 +29,35 @@ import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Getter
@Setter(AccessLevel.PRIVATE)
@MavenLibrary(groupId = "org\\.sqlite", artifactId ="sqlite-jdbc", version = "3.48.0.0", relocations = {
@Relocate(from ="org" + ".\\sqlite", to ="dev.brighten.antivpn.shaded.org.sqlite")})
@MavenLibrary(groupId = "com.h2database", artifactId ="h2", version = "2.2.220", relocations = {
@Relocate(from ="org" + ".\\h2", to ="dev.brighten.antivpn.shaded.org.h2")})
@MavenLibrary(groupId = "org.mongodb", artifactId = "mongo-java-driver", version = "3.12.14", relocations = {
@Relocate(from = "com." + "\\mongodb", to = "dev.brighten.antivpn.shaded.com.mongodb"),
@Relocate(from = "org" + "\\.bson", to = "dev.brighten.antivpn.shaded.org.bson")
})
@MavenLibrary(
groupId = "com.mysql",
artifactId = "mysql-connector-j",
version = "9.1.0",
relocations = {
@Relocate(from = "com.my\\" + "sql.cj", to = "dev.brighten.antivpn.shaded.com.mysql.cj"),
@Relocate(from = "com.my\\" + "sql.jdbc", to = "dev.brighten.antivpn.shaded.com.mysql.jdbc")
}
)
@MavenLibrary(groupId = "com.\\github\\.ben-manes\\.caffeine", artifactId = "caffeine", version = "3.1.8",
relocations = {
@Relocate(from = "com\\.github\\.benmanes\\.caffeine", to = "dev.brighten.antivpn.shaded.com.github.benmanes.caffeine"),
})
@MavenLibrary(groupId = "org\\.postgresql", artifactId = "postgresql", version = "42.7.6",
relocations = {
@Relocate(from = "org\\.postgresql", to = "dev.brighten.antivpn.shaded.org.postgresql")
})
@MavenLibrary(groupId = "com\\.mongodb", artifactId = "driver-sync", version = "5.5.0",
relocations = {
@Relocate(from = "com\\.mongodb.client", to = "dev.brighten.antivpn.shaded.com.mongodb.client")
})
public class AntiVPN {
private static AntiVPN INSTANCE;
private VPNConfig vpnConfig;
private VPNExecutor executor;
private PlayerExecutor playerExecutor;
private Database database;
private VPNDatabase database;
private MessageHandler messageHandler;
private Configuration config;
private List<Command> commands = new ArrayList<>();
@@ -72,8 +75,6 @@ public class AntiVPN {
LibraryLoader.loadAll(INSTANCE);
Version.register();
try {
File configFile = new File(pluginFolder, "config.yml");
if(!configFile.exists()){
@@ -97,16 +98,41 @@ public class AntiVPN {
INSTANCE.messageHandler = new MessageHandler();
INSTANCE.database = switch (INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) {
case "sqlite", "sqllite" -> new LiteDatabase();
case "postgresql", "postgres" -> new PostgresDatabase();
case "mongo", "mongodb" -> new MongoDatabase();
default ->
throw new IllegalStateException("Unexpected database type set at config.yml 'database.type': \""
+ INSTANCE.vpnConfig.getDatabaseType().toLowerCase() + "\"!" +
"Available types: 'sqlite', 'postgresql', 'mongodb'");
};
INSTANCE.database.init();
try {
switch(INSTANCE.vpnConfig.getDatabaseType().toLowerCase()) {
case "h2":
case "local":
case "flatfile": {
AntiVPN.getInstance().getExecutor().log("Using databaseType H2...");
INSTANCE.database = new H2VPN();
INSTANCE.database.init();
break;
}
case "mysql":
case "sql":{
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN();
INSTANCE.database.init();
break;
}
case "mongo":
case "mongodb":
case "mongod": {
INSTANCE.database = new MongoVPN();
INSTANCE.database.init();
break;
}
default: {
AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " +
"Options: [MySQL]");
break;
}
}
} catch (Exception e) {
AntiVPN.getInstance().getExecutor().logException("Could not initialize database, plugin disabling...", e);
executor.disablePlugin();
return;
}
//Registering commands
INSTANCE.registerCommands();
@@ -117,8 +143,7 @@ public class AntiVPN {
//of unnecessary database queries.
if(player.hasPermission("antivpn.command.alerts")) {
//Running database check for enabled alerts.
INSTANCE.database.updateAlertsState(player.getUuid(), true);
player.setAlertsEnabled(true);
INSTANCE.database.alertsState(player.getUuid(), player::setAlertsEnabled);
}
});
@@ -129,8 +154,6 @@ public class AntiVPN {
// Starting kick checks
AntiVPN.getInstance().getExecutor().startKickChecks();
AntiVPN.getInstance().runSpoiledResponseChecks();
}
public InputStream getResource(String filename) {
@@ -153,23 +176,56 @@ public class AntiVPN {
}
public void stop() {
executor.shutdown();
if (database instanceof H2VPN) {
database.shutdown();
// Try to deregister driver
try {
java.sql.Driver driver = java.sql.DriverManager.getDriver("jdbc:h2:");
if (driver != null) {
java.sql.DriverManager.deregisterDriver(driver);
}
} catch (Exception e) {
// Log but don't throw
executor.log("Failed to deregister H2 driver: " + e.getMessage());
}
}
VPNExecutor.threadExecutor.shutdown();
if(database != null) database.shutdown();
}
private void runSpoiledResponseChecks() {
if(database == null) return;
AntiVPN.getInstance().getExecutor().getThreadExecutor().scheduleAtFixedRate(
() -> database.clearOutdatedResponses(),
0, 30, TimeUnit.MINUTES
);
}
public void reloadDatabase() {
database.shutdown();
INSTANCE.database = new LiteDatabase();
switch(AntiVPN.getInstance().getVpnConfig().getDatabaseType().toLowerCase()) {
case "h2":
case "local":
case "flatfile": {
AntiVPN.getInstance().getExecutor().log("Using databaseType H2...");
INSTANCE.database = new H2VPN();
INSTANCE.database.init();
break;
}
case "mysql":
case "sql":{
AntiVPN.getInstance().getExecutor().log("Using databaseType MySQL...");
INSTANCE.database = new MySqlVPN();
INSTANCE.database.init();
break;
}
case "mongo":
case "mongodb":
case "mongod": {
INSTANCE.database = new MongoVPN();
INSTANCE.database.init();
break;
}
default: {
AntiVPN.getInstance().getExecutor().log("Could not find database type \"" + INSTANCE.vpnConfig.getDatabaseType() + "\". " +
"Options: [MySQL]");
break;
}
}
}
public static AntiVPN getInstance() {
@@ -200,4 +256,4 @@ public class AntiVPN {
private void registerCommands() {
commands.add(new AntiVPNCommand());
}
}
}
@@ -2,19 +2,17 @@ package dev.brighten.antivpn.api;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.utils.ConfigDefault;
import lombok.Getter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class VPNConfig {
private final ConfigDefault<String> licenseDefault = new ConfigDefault<>("",
"license", AntiVPN.getInstance()), kickStringDefault =
new ConfigDefault<>("Proxies are not allowed on our server",
"kickMessage", AntiVPN.getInstance()),
defaultDatabaseType = new ConfigDefault<>("SQLite",
defaultDatabaseType = new ConfigDefault<>("H2",
"database.type", AntiVPN.getInstance()),
defaultDatabaseName = new ConfigDefault<>("kaurivpn",
"database.database", AntiVPN.getInstance()),
@@ -54,22 +52,212 @@ public class VPNConfig {
defCountrylist = new ConfigDefault<>(new ArrayList<>(), "countries.list",
AntiVPN.getInstance());
/**
* -- GETTER --
* License from <a href="https://funkemunky.cc/shop">...</a> to be used for more queries.
*
*/
@Getter
private String license, kickMessage, databaseType, databaseName, mongoURL, username, password,
ip, alertMsg, countryVanillaKickReason;
@Getter
private String license, kickMessage, databaseType, databaseName, mongoURL, username, password, ip, alertMsg,
countryVanillaKickReason;
private List<String> prefixWhitelists, commands, countryList, countryKickCommands;
@Getter
private int port;
@Getter
private boolean cacheResults, databaseEnabled, useCredentials, commandsEnabled, kickPlayers, alertToStaff,
metrics, whitelistCountries;
/**
* License from https://funkemunky.cc/shop to be used for more queries.
* @return String
*/
public String getLicense() {
return license;
}
/**
* If true, results will be cached to reduce queries to https://funkemunky.cc
* @return boolean
*/
public boolean cachedResults() {
return cacheResults;
}
/**
* Will be used for vanilla kick message when {@link VPNConfig#runCommands()} is true.
* @return String
*/
public String getKickString() {
return kickMessage;
}
/**
* Message to send staff on proxy detection.
* @return String
*/
public String alertMessage() {
return alertMsg;
}
/**
* If true, staff will be alerted on proxy detection.
* @return boolean
*/
public boolean alertToStaff() {
return alertToStaff;
}
/**
* If true, will run {@link VPNConfig#commands()} on detect. If not, it will use vanilla kicking methods.
* @return boolean
*/
public boolean runCommands() {
return commandsEnabled;
}
/**
* Commands to run on proxy detection.
* @return List
*/
public List<String> commands() {
return commands;
}
/**
* If false, no commands nor kick will be run on proxy detection.
* @return boolean
*/
public boolean kickPlayersOnDetect() {
return kickPlayers;
}
/**
* Returns Strings of which are checked against the beginning of player names. Used to
* allow Geyser-connected players to join.
* @return List
*/
public List<String> getPrefixWhitelists() {
return prefixWhitelists;
}
/**
* Returns true if we want to use a database
* @return boolean
*/
public boolean isDatabaseEnabled() {
return databaseEnabled;
}
/**
* Whether or not the database we want to connect to requires credentials.
* @return boolean
*/
public boolean useDatabaseCreds() {
return useCredentials;
}
/**
* Only for Mongo only. URL used for connecting to database. Overrides other fields
* @return String
*/
public String mongoDatabaseURL() {
return mongoURL;
}
/**
* Database type. Either MySQL and Mongo.
* @return String
*/
public String getDatabaseType() {
return databaseType;
}
/**
* Database name
* @return String
*/
public String getDatabaseName() {
return databaseName;
}
/**
* Database username
* @return String
*/
public String getUsername() {
return username;
}
/**
* Database Password
* @return String
*/
public String getPassword() {
return password;
}
/**
* Database IP
* @return String
*/
public String getIp() {
return ip;
}
/**
* Returns the list of ISO country codes we need to check.
* @return List
*/
public List<String> countryList() {
return countryList;
}
/**
* If true, we only allow the {@link VPNConfig#countryKickCommands()}. If false, we blacklist them.
* @return boolean
*/
public boolean whitelistCountries() {
return whitelistCountries;
}
/**
* Returns our configured commands to run on player country detection.
* @return List
*/
public List<String> countryKickCommands() {
return countryKickCommands;
}
/**
* Returns the vanilla kick reason for bad country locations
* @return String
*/
public String countryVanillaKickReason() {
return countryVanillaKickReason;
}
/**
* Gets the port based on configuration. If {@link VPNConfig#port} is -1, will get default port
* based on {@link VPNConfig#getDatabaseType()} lowerCase().
* @return int
*/
public int getPort() {
if(port == -1) {
switch (getDatabaseType().toLowerCase()) {
case "mongodb":
case "mongo":
case "mongod":
return 27017;
case "sql":
case "mysql":
return 3306;
}
}
return port;
}
/**
* If true, https://bstats.org metrics will be collected to improve KauriVPN.
* @return boolean
*/
public boolean metrics() {
return metrics;
}
/**
* Grabs all information from the config.yml
*/
@@ -99,4 +287,4 @@ public class VPNConfig {
countryVanillaKickReason = defaultCountryKickReason.get();
}
}
}
@@ -17,13 +17,10 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public abstract class VPNExecutor {
@Getter
private final ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2);
public static ScheduledExecutorService threadExecutor = Executors.newScheduledThreadPool(2);
@Getter
private final Set<UUID> whitelisted = Collections.synchronizedSet(new HashSet<>());
@Getter
private final Set<String> whitelistedIps = Collections.synchronizedSet(new HashSet<>());
@@ -32,10 +29,6 @@ public abstract class VPNExecutor {
public abstract void registerListeners();
public void shutdown() {
threadExecutor.shutdown();
}
public abstract void log(Level level, String log, Object... objects);
public abstract void log(String log, Object... objects);
@@ -73,35 +66,35 @@ public abstract class VPNExecutor {
}
public void handleKickingOfPlayer(CheckResult result, APIPlayer player) {
if (AntiVPN.getInstance().getVpnConfig().isAlertToStaff()) AntiVPN.getInstance().getPlayerExecutor()
if (AntiVPN.getInstance().getVpnConfig().alertToStaff()) AntiVPN.getInstance().getPlayerExecutor()
.getOnlinePlayers()
.stream()
.filter(APIPlayer::isAlertsEnabled)
.forEach(pl ->
pl.sendMessage(StringUtil.translateAlternateColorCodes('&',
StringUtil.varReplace(dev.brighten.antivpn.AntiVPN.getInstance().getVpnConfig()
.getAlertMsg(), player, result.response()))));
.alertMessage(), player, result.response()))));
if(AntiVPN.getInstance().getVpnConfig().isKickPlayers()) {
if(AntiVPN.getInstance().getVpnConfig().kickPlayersOnDetect()) {
switch (result.resultType()) {
case DENIED_PROXY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getKickMessage(), player, result.response()));
.getKickString(), player, result.response()));
case DENIED_COUNTRY -> player.kickPlayer(StringUtil.varReplace(AntiVPN.getInstance().getVpnConfig()
.getCountryVanillaKickReason(), player, result.response()));
.countryVanillaKickReason(), player, result.response()));
}
}
if(!AntiVPN.getInstance().getVpnConfig().isCommandsEnabled()) return;
if(!AntiVPN.getInstance().getVpnConfig().runCommands()) return;
switch (result.resultType()) {
case DENIED_PROXY -> {
for (String command : AntiVPN.getInstance().getVpnConfig().getCommands()) {
for (String command : AntiVPN.getInstance().getVpnConfig().commands()) {
runCommand(StringUtil.translateAlternateColorCodes('&',
StringUtil.varReplace(command, player, result.response())));
}
}
case DENIED_COUNTRY -> {
for (String command : AntiVPN.getInstance().getVpnConfig().getCountryKickCommands()) {
for (String command : AntiVPN.getInstance().getVpnConfig().countryKickCommands()) {
runCommand(StringUtil.translateAlternateColorCodes('&',
StringUtil.varReplace(command, player, result.response())));
}
@@ -41,4 +41,4 @@ public interface VPNDatabase {
void init();
void shutdown();
}
}
@@ -9,6 +9,7 @@ import dev.brighten.antivpn.database.sql.utils.MySQL;
import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.web.objects.VPNResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.sql.ResultSet;
import java.sql.SQLException;
@@ -87,13 +88,17 @@ public class H2VPN implements VPNDatabase {
cachedResponses.put(toCache.getIp(), toCache);
Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude()).append(toCache.getLongitude()).execute();
try {
Query.prepare("insert into `responses` (`ip`,`asn`,`countryName`,`countryCode`,`city`,`timeZone`,"
+ "`method`,`isp`,`proxy`,`cached`,`inserted`,`latitude`,`longitude`) values (?,?,?,?,?,?,?,?,?,?,?,?,?)")
.append(toCache.getIp()).append(toCache.getAsn()).append(toCache.getCountryName())
.append(toCache.getCountryCode()).append(toCache.getCity()).append(toCache.getTimeZone())
.append(toCache.getMethod()).append(toCache.getIsp()).append(toCache.isProxy())
.append(toCache.isCached()).append(new Timestamp(System.currentTimeMillis()))
.append(toCache.getLatitude()).append(toCache.getLongitude()).execute();
} catch(SQLException e) {
}
}
@Override
@@ -101,18 +106,24 @@ public class H2VPN implements VPNDatabase {
if(!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return;
Query.prepare("delete from `responses` where `ip` = ?").append(ip).execute();
try {
Query.prepare("delete from `responses` where `ip` = ?").append(ip).execute();
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not delete response from IP: " + ip, e);
}
}
@SneakyThrows
@Override
public boolean isWhitelisted(UUID uuid) {
if (!AntiVPN.getInstance().getVpnConfig().isDatabaseEnabled() || MySQL.isClosed())
return false;
ResultSet set = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString()).executeQuery();
return set != null && set.next() && set.getString("uuid") != null;
try(ResultSet set = Query.prepare("select uuid from `whitelisted` where `uuid` = ? limit 1")
.append(uuid.toString()).executeQuery()) {
return set != null && set.next() && set.getString("uuid") != null;
} catch (SQLException e) {
AntiVPN.getInstance().getExecutor().logException("Could not check whitelist for uuid '" + uuid + "' due to SQL error.", e);
return false;
}
}
@SneakyThrows
@@ -252,25 +263,6 @@ public class H2VPN implements VPNDatabase {
AntiVPN.getInstance().getExecutor().log("Creating tables...");
//Running check for old table types to update
Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)").execute();
Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)").execute();
Query.prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12),"
+ "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
+ "`latitude` double, `longitude` double)").execute();
Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)").execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
try {
Query.prepare("create index if not exists `uuid_1` on `whitelisted` (`uuid`)").execute();
Query.prepare("create index if not exists `ip_1` on `responses` (`ip`)").execute();
Query.prepare("create index if not exists `proxy_1` on `responses` (`proxy`)").execute();
Query.prepare("create index if not exists `inserted_1` on `responses` (`inserted`)").execute();
Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)").execute();
} catch (Exception e) {
System.err.println("MySQL Excepton created" + e.getMessage());
}
}
@Override
@@ -0,0 +1,40 @@
package dev.brighten.antivpn.database.local.version;
import dev.brighten.antivpn.AntiVPN;
import dev.brighten.antivpn.database.local.H2VPN;
import dev.brighten.antivpn.database.sql.utils.Query;
import dev.brighten.antivpn.database.version.H2Version;
public class First implements H2Version {
@Override
public void update(H2VPN database) throws Exception {
Query.prepare("create table if not exists `whitelisted` (`uuid` varchar(36) not null)").execute();
Query.prepare("create table if not exists `whitelisted-ips` (`ip` varchar(45) not null)").execute();
Query.prepare("create table if not exists `responses` (`ip` varchar(45) not null, `asn` varchar(12),"
+ "`countryName` text, `countryCode` varchar(10), `city` text, `timeZone` varchar(64), "
+ "`method` varchar(32), `isp` text, `proxy` boolean, `cached` boolean, `inserted` timestamp,"
+ "`latitude` double, `longitude` double)").execute();
Query.prepare("create table if not exists `alerts` (`uuid` varchar(36) not null)").execute();
AntiVPN.getInstance().getExecutor().log("Creating indexes...");
try {
Query.prepare("create index if not exists `uuid_1` on `whitelisted` (`uuid`)").execute();
Query.prepare("create index if not exists `ip_1` on `responses` (`ip`)").execute();
Query.prepare("create index if not exists `proxy_1` on `responses` (`proxy`)").execute();
Query.prepare("create index if not exists `inserted_1` on `responses` (`inserted`)").execute();
Query.prepare("create index if not exists `ip_1` on `whitelisted-ips` (`ip`)").execute();
} catch (Exception e) {
System.err.println("MySQL Excepton created" + e.getMessage());
}
}
@Override
public int versionNumber() {
return 0;
}
@Override
public boolean needsUpdate(H2VPN database) {
return false;
}
}
@@ -1,20 +1,20 @@
package dev.brighten.antivpn.database.sql.utils;
import lombok.Getter;
import org.intellij.lang.annotations.Language;
import java.sql.Connection;
import java.sql.SQLException;
public class Query {
@Getter
private static Connection conn;
public static void use(Connection conn) {
Query.conn = conn;
}
public static ExecutableStatement prepare(String query) {
try {
return new ExecutableStatement(conn.prepareStatement(query));
} catch (SQLException e) {
throw new RuntimeException(e);
}
public static ExecutableStatement prepare(@Language("SQL") String sql) throws SQLException {
return new ExecutableStatement(conn.prepareStatement(sql));
}
}
@@ -0,0 +1,15 @@
package dev.brighten.antivpn.database.version;
import dev.brighten.antivpn.database.local.H2VPN;
import java.util.ArrayList;
import java.util.List;
public interface H2Version extends Version<H2VPN> {
List<H2Version> versions = new ArrayList<>();
static void registerVersions() {
}
}
@@ -0,0 +1,7 @@
package dev.brighten.antivpn.database.version;
public interface Version<DB> {
void update(DB database) throws Exception;
int versionNumber();
boolean needsUpdate(DB database);
}
@@ -0,0 +1,146 @@
/*
* The MIT License
*
* Copyright (c) 2013 Edin Dazdarevic (edin.dazdarevic@gmail.com)
* 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.
*
* */
package dev.brighten.antivpn.utils;
import lombok.Getter;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;
/**
* A class that enables to get an IP range from CIDR specification. It supports
* both IPv4 and IPv6.
*/
@Getter
public class CIDRUtils {
private final String cidr;
private final InetAddress inetAddress;
private InetAddress startAddress;
private InetAddress endAddress;
private final int prefixLength;
public CIDRUtils(String cidr) throws UnknownHostException {
this.cidr = cidr;
/* split CIDR to address and prefix part */
if (this.cidr.contains("/")) {
int index = this.cidr.indexOf("/");
String addressPart = this.cidr.substring(0, index);
String networkPart = this.cidr.substring(index + 1);
inetAddress = InetAddress.getByName(addressPart);
prefixLength = Integer.parseInt(networkPart);
calculate();
} else {
throw new IllegalArgumentException("not an valid CIDR format!");
}
}
private void calculate() throws UnknownHostException {
ByteBuffer maskBuffer;
int targetSize;
if (inetAddress.getAddress().length == 4) {
maskBuffer =
ByteBuffer
.allocate(4)
.putInt(-1);
targetSize = 4;
} else {
maskBuffer = ByteBuffer.allocate(16)
.putLong(-1L)
.putLong(-1L);
targetSize = 16;
}
BigInteger mask = (new BigInteger(1, maskBuffer.array())).not().shiftRight(prefixLength);
ByteBuffer buffer = ByteBuffer.wrap(inetAddress.getAddress());
BigInteger ipVal = new BigInteger(1, buffer.array());
BigInteger startIp = ipVal.and(mask);
BigInteger endIp = startIp.add(mask.not());
byte[] startIpArr = toBytes(startIp.toByteArray(), targetSize);
byte[] endIpArr = toBytes(endIp.toByteArray(), targetSize);
this.startAddress = InetAddress.getByAddress(startIpArr);
this.endAddress = InetAddress.getByAddress(endIpArr);
}
private byte[] toBytes(byte[] array, int targetSize) {
int counter = 0;
List<Byte> newArr = new ArrayList<Byte>();
while (counter < targetSize && (array.length - 1 - counter >= 0)) {
newArr.add(0, array[array.length - 1 - counter]);
counter++;
}
int size = newArr.size();
for (int i = 0; i < (targetSize - size); i++) {
newArr.add(0, (byte) 0);
}
byte[] ret = new byte[newArr.size()];
for (int i = 0; i < newArr.size(); i++) {
ret[i] = newArr.get(i);
}
return ret;
}
public String getNetworkAddress() {
return this.startAddress.getHostAddress();
}
public String getBroadcastAddress() {
return this.endAddress.getHostAddress();
}
public boolean isInRange(String ipAddress) throws UnknownHostException {
InetAddress address = InetAddress.getByName(ipAddress);
BigInteger start = new BigInteger(1, this.startAddress.getAddress());
BigInteger end = new BigInteger(1, this.endAddress.getAddress());
BigInteger target = new BigInteger(1, address.getAddress());
int st = start.compareTo(target);
int te = target.compareTo(end);
return (st == -1 || st == 0) && (te == -1 || te == 0);
}
}