Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion TaskDaemon.Core/src/me/aa07/paradise/taskdaemon/core/Core.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.Optional;
import me.aa07.paradise.taskdaemon.core.config.ConfigHolder;
import me.aa07.paradise.taskdaemon.core.database.DbCore;
import me.aa07.paradise.taskdaemon.core.modules.aclcleanup.AclCleanupJob;
import me.aa07.paradise.taskdaemon.core.modules.bouncerrestart.BouncerRestartJob;
import me.aa07.paradise.taskdaemon.core.modules.ip2asn.Ip2AsnJob;
import me.aa07.paradise.taskdaemon.core.modules.profilercleanup.ProfilerCleanupJob;
Expand Down Expand Up @@ -81,6 +82,20 @@ private void setupJobs(Scheduler scheduler, DbCore dbCore, ConfigHolder config,
// See below for CRON format
// https://www.quartz-scheduler.org/documentation/quartz-2.3.0/tutorials/crontrigger.html

// ACL cleanup
JobDataMap jdm_aclcleanup = new JobDataMap();
jdm_aclcleanup.put("LOGGER", logger);
jdm_aclcleanup.put("DBCORE", dbCore);
jdm_aclcleanup.put("PFSENSE_CFG", config.pfsense);
JobDetail jd_aclcleanup = JobBuilder.newJob(AclCleanupJob.class)
.withIdentity("aclcleanup", "aclcleanup")
.usingJobData(jdm_aclcleanup)
.build();
CronTrigger ct_aclcleanup = TriggerBuilder.newTrigger()
.withIdentity("aclcleanup", "aclcleanup")
.withSchedule(CronScheduleBuilder.cronSchedule("0 */10 * * * ?"))
.build();

// Bouncer restart
JobDataMap jdm_bouncerrestart = new JobDataMap();
jdm_bouncerrestart.put("LOGGER", logger);
Expand Down Expand Up @@ -121,8 +136,8 @@ private void setupJobs(Scheduler scheduler, DbCore dbCore, ConfigHolder config,
.withSchedule(CronScheduleBuilder.cronSchedule("0 0 8 * * ?")) // Every day - 8AM
.build();


// Schedule all
scheduler.scheduleJob(jd_aclcleanup, ct_aclcleanup);
scheduler.scheduleJob(jd_bouncerrestart, ct_bouncerrestart);
scheduler.scheduleJob(jd_ip2asn, ct_ip2asn);
scheduler.scheduleJob(jd_profilercleanup, ct_profilercleanup);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
public class ConfigHolder {
public DatabaseConfig gameDatabase;
public Ip2AsnSerivceConfig ip2asn;
public PfsenseConfig pfsense;
public DatabaseConfig profilerDatabase;
public RedisConfig redis;
public TgsConfig tgs;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package me.aa07.paradise.taskdaemon.core.config;

public class PfsenseConfig{
public String host;
public int port;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
package me.aa07.paradise.taskdaemon.core.modules.aclcleanup;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import me.aa07.paradise.taskdaemon.core.config.PfsenseConfig;
import me.aa07.paradise.taskdaemon.core.database.DatabaseType;
import me.aa07.paradise.taskdaemon.core.database.DbCore;
import me.aa07.paradise.taskdaemon.database.gamedb.Tables;
import org.apache.logging.log4j.Logger;
import org.jooq.DSLContext;
import org.quartz.Job;
import org.quartz.JobDataMap;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

/**
* Scheduled job that cleans up ACL by removing inactive player IPs
*/
public class AclCleanupJob implements Job {

/**
* Main job execution - fetches current ACL, checks database for inactive IPs, and removes them
*/
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
JobDataMap datamap = context.getMergedJobDataMap();

// Get our logger - important
Object raw_logger = datamap.get("LOGGER");
Optional<Logger> logger_holder = Optional.empty();

if (raw_logger instanceof Logger l2) {
logger_holder = Optional.of(l2);
}

if (!logger_holder.isPresent()) {
System.out.println("[AclCleanup] LOGGER WAS SOMEHOW NULL - THIS IS VERY BAD");
return;
}

Logger logger = logger_holder.get();

// Now get our DB
Object raw_db = datamap.get("DBCORE");
Optional<DbCore> dbcore_holder = Optional.empty();

if (raw_db instanceof DbCore db2) {
dbcore_holder = Optional.of(db2);
}

if (!dbcore_holder.isPresent()) {
logger.error("[AclCleanup] DBCORE WAS SOMEHOW NULL - THIS IS VERY BAD");
return;
}

DbCore dbcore = dbcore_holder.get();

// Get pfSense configuration
Object raw_pfs_cfg = datamap.get("PFSENSE_CFG");
Optional<PfsenseConfig> pfs_cfg_holder = Optional.empty();

if (raw_pfs_cfg instanceof PfsenseConfig pfsCfg) {
pfs_cfg_holder = Optional.of(pfsCfg);
}

if (pfs_cfg_holder.isEmpty()) {
logger.error("[AclCleanup] PFSENSE_CFG WAS SOMEHOW NULL - THIS IS VERY BAD");
return;
}

PfsenseConfig pfs_cfg = pfs_cfg_holder.get();

try {
List<String> current_acl_ips = fetchAcl(logger, pfs_cfg);
logger.info("IPs in ACL ({}): {}", current_acl_ips.size(), String.join(" ", current_acl_ips));

List<String> ips_to_remove = checkIpsInDatabase(current_acl_ips, logger, dbcore);

if (ips_to_remove.isEmpty()) {
logger.info("No IPs to remove from ACL");
} else {
logger.info("IPs to remove from ACL ({}): {}", ips_to_remove.size(), String.join(" ", ips_to_remove));

for (String ip : ips_to_remove) {
removeIpFromAcl(ip, logger, pfs_cfg);
}

logger.info("Removed {} IPs from ACL", ips_to_remove.size());
}

List<String> updated_acl_ips = fetchAcl(logger, pfs_cfg);
logger.info("IPs now in ACL ({}): {}", updated_acl_ips.size(), String.join(" ", updated_acl_ips));

} catch (IOException e) {
logger.error("Error in AclCleanupJob!");
logger.error(e);
}
}

/**
* Checks which IPs should be removed from ACL based on database activity
* Removes IPs that don't exist in DB or haven't been seen in last 10 minutes
*/
public List<String> checkIpsInDatabase(List<String> ips, Logger logger, DbCore dbcore) {
List<String> to_remove = new ArrayList<>();

DSLContext ctx = dbcore.jooq(DatabaseType.GameDb);
LocalDateTime ten_mins_ago = dbcore.now().minusMinutes(10);

for (String ip : ips) {
if (!ctx.fetchExists(ctx.select(Tables.PLAYER.CKEY).from(Tables.PLAYER).where(Tables.PLAYER.IP.eq(ip)))) {
logger.info("IP {} no longer in database.", ip);
to_remove.add(ip);
continue;
}

boolean seen_in_last_10m = ctx.fetchExists(
ctx.select(Tables.PLAYER.CKEY).from(Tables.PLAYER).where(Tables.PLAYER.IP.eq(ip))
.and(Tables.PLAYER.LASTSEEN.lt(ten_mins_ago))
);

if (!seen_in_last_10m) {
logger.info("IP {} not active within last 10 minutes.", ip);
to_remove.add(ip);
}
}

return to_remove;
}

/**
* Retrieves current IP list from ACL via socket connection
*/
public List<String> fetchAcl(Logger logger, PfsenseConfig cfg) throws IOException {
List<String> ip_list = new ArrayList<>();

Socket socket = new Socket(cfg.host, cfg.port);
socket.setSoTimeout(10000);

OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();

String command = "show acl #1\n";
out.write(command.getBytes("ascii"));

StringBuilder retval = new StringBuilder();
byte[] buffer = new byte[16];
int bytes_read;

while ((bytes_read = in.read(buffer)) != -1) {
retval.append(new String(buffer, 0, bytes_read));
}

socket.close();

String[] raw_ips = retval.toString().split("\n");
for (String ip : raw_ips) {
if (ip.trim().isEmpty()) {
continue;
}

String[] parts = ip.split(" ");
if (parts.length >= 2) {
ip_list.add(parts[1]);
}
}

return ip_list;
}

/**
* Removes a specific IP from ACL #1 via socket command
*/
public void removeIpFromAcl(String ip, Logger logger, PfsenseConfig cfg) throws IOException {
logger.info("Removing {} from ACL", ip);

Socket socket = new Socket(cfg.host, cfg.port);
OutputStream out = socket.getOutputStream();

String command = "del acl #1 " + ip + "\n";
out.write(command.getBytes("ascii"));

socket.close();
}
}
4 changes: 4 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ database = "paradise_profilerdaemon"
[ip2asn]
host = "http://127.0.0.1:53661/v1/as/ip/" # Fill out URL - IP will be appended at the end

[pfsense]
host = "127.0.0.1"
port = 1234

[profilerDatabase]
host = "172.16.0.200"
username = "myuser"
Expand Down