All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.aoindustries.aoserv.daemon.iptables.IpReputationManager Maven / Gradle / Ivy

/*
 * aoserv-daemon - Server management daemon for the AOServ Platform.
 * Copyright (C) 2012, 2013, 2016, 2017, 2018, 2019, 2020, 2021, 2022  AO Industries, Inc.
 *     [email protected]
 *     7262 Bull Pen Cir
 *     Mobile, AL 36695
 *
 * This file is part of aoserv-daemon.
 *
 * aoserv-daemon is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * aoserv-daemon is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with aoserv-daemon.  If not, see .
 */

package com.aoindustries.aoserv.daemon.iptables;

import com.aoapps.collections.AoCollections;
import com.aoapps.io.posix.PosixFile;
import com.aoapps.lang.math.SafeMath;
import com.aoindustries.aoserv.client.AoservConnector;
import com.aoindustries.aoserv.client.account.Administrator;
import com.aoindustries.aoserv.client.distribution.OperatingSystemVersion;
import com.aoindustries.aoserv.client.linux.Server;
import com.aoindustries.aoserv.client.master.User;
import com.aoindustries.aoserv.client.net.reputation.Host;
import com.aoindustries.aoserv.client.net.reputation.Network;
import com.aoindustries.aoserv.client.net.reputation.Set;
import com.aoindustries.aoserv.daemon.AoservDaemon;
import com.aoindustries.aoserv.daemon.AoservDaemonConfiguration;
import com.aoindustries.aoserv.daemon.backup.BackupManager;
import com.aoindustries.aoserv.daemon.util.BuilderThread;
import java.io.File;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Handles the IP reputation system.
 *
 * @author  AO Industries, Inc.
 */
public final class IpReputationManager extends BuilderThread {

  private static final Logger logger = Logger.getLogger(IpReputationManager.class.getName());

  private static IpReputationManager ipReputationManager;

  private static final String IPTABLES_DIR = "/etc/opt/aoserv-daemon/iptables";
  private static final String IPREPUTATION_SUBDIR = "ipreputation";

  /**
   * Gets the iptables directory, creating if necessary.
   */
  private static PosixFile getIptablesDir() throws IOException {
    PosixFile iptablesDir = new PosixFile(IPTABLES_DIR);
    if (!iptablesDir.getStat().exists()) {
      iptablesDir.mkdir(false, 0700);
    }
    return iptablesDir;
  }

  /**
   * Gets the iptables directory, creating if necessary.
   */
  private static PosixFile getIpreputationDir() throws IOException {
    PosixFile iptablesDir = getIptablesDir();
    PosixFile ipreputationDir = new PosixFile(iptablesDir.getPath() + "/" + IPREPUTATION_SUBDIR);
    if (!ipreputationDir.getStat().exists()) {
      ipreputationDir.mkdir(false, 0700);
    }
    return ipreputationDir;
  }

  private IpReputationManager() {
    // Do nothing
  }

  @SuppressWarnings("UseOfSystemOutOrSystemErr")
  public static void start() throws IOException, SQLException {
    Server thisServer = AoservDaemon.getThisServer();
    OperatingSystemVersion osv = thisServer.getHost().getOperatingSystemVersion();
    int osvId = osv.getPkey();

    synchronized (System.out) {
      if (
          // Nothing is done for these operating systems
          osvId != OperatingSystemVersion.CENTOS_5_I686_AND_X86_64
              && osvId != OperatingSystemVersion.CENTOS_7_X86_64
              // Check config after OS check so config entry not needed
              && AoservDaemonConfiguration.isManagerEnabled(IpReputationManager.class)
              && ipReputationManager == null
      ) {
        System.out.print("Starting IpReputationManager: ");
        // Must be a supported operating system
        if (
            // Only runs on Xen dom0 (firewalling done outside virtual servers)
            osvId == OperatingSystemVersion.CENTOS_5_DOM0_I686
                || osvId == OperatingSystemVersion.CENTOS_5_DOM0_X86_64
        ) {
          AoservConnector conn = AoservDaemon.getConnector();
          Administrator administrator = conn.getCurrentAdministrator();
          User mu = administrator.getMasterUser();
          if (mu == null) {
            throw new AssertionError("Administrator is not a User");
          }
          if (mu.isRouter()) {
            ipReputationManager = new IpReputationManager();
            conn.getNet().getReputation().getSet().addTableListener(ipReputationManager, 0);
            conn.getNet().getReputation().getHost().addTableListener(ipReputationManager, 0);
            conn.getNet().getReputation().getNetwork().addTableListener(ipReputationManager, 0);
            System.out.println("Done");
          } else {
            System.out.println("Disabled: This is not a router");
          }
        } else {
          System.out.println("Unsupported OperatingSystemVersion: " + osv);
        }
      }
    }
  }

  @Override
  public String getProcessTimerDescription() {
    return "Rebuild IP Reputation Sets";
  }

  /**
   * Orders hosts with worse reputation first.
   */
  private static final Comparator badHostComparator = (Host host1, Host host2) -> {
    // Sort by effective reputation first
    int rep1 = host1.getReputation();
    int rep2 = host2.getReputation();
    if (rep1 < rep2) {
      return -1;
    }
    if (rep1 > rep2) {
      return 1;
    }
    // Sort by IP next
    int ip1 = host1.getHost();
    int ip2 = host2.getHost();
    if (ip1 < ip2) {
      return -1;
    }
    if (ip1 > ip2) {
      return 1;
    }
    return 0;
  };

  /**
   * Orders networks with best reputation first.
   */
  private static final Comparator goodNetworkComparator = (Network network1, Network network2) -> {
    // Sort by effective reputation first
    int count1 = network1.getCounter();
    int count2 = network2.getCounter();
    if (count1 > count2) {
      return -1;
    }
    if (count1 < count2) {
      return 1;
    }
    // Sort by IP next
    int ipNet1 = network1.getNetwork();
    int ipNet2 = network2.getNetwork();
    if (ipNet1 < ipNet2) {
      return -1;
    }
    if (ipNet1 > ipNet2) {
      return 1;
    }
    return 0;
  };

  /**
   * Orders hosts with best reputation first.
   */
  private static final Comparator goodHostComparator = (Host host1, Host host2) -> {
    // Sort by effective reputation first
    int rep1 = host1.getReputation();
    int rep2 = host2.getReputation();
    if (rep1 > rep2) {
      return -1;
    }
    if (rep1 < rep2) {
      return 1;
    }
    // Sort by IP next
    int ip1 = host1.getHost();
    int ip2 = host2.getHost();
    if (ip1 < ip2) {
      return -1;
    }
    if (ip1 > ip2) {
      return 1;
    }
    return 0;
  };

  /**
   * See ip_reputation_sets-create.sql for set name encoding
   *
   * @see  #synchronizeIpset
   */
  private static void synchronizeHostIpset(
      java.util.Set hosts,
      Set.ConfidenceType confidence,
      Set.ReputationType reputationType,
      String identifier,
      PosixFile setDir
  ) throws IOException {
    java.util.Set entries = AoCollections.newLinkedHashSet(Math.min(Ipset.MAX_IPSET_SIZE + 1, hosts.size()));
    for (Host host : hosts) {
      entries.add(host.getHost());
      if (entries.size() > Ipset.MAX_IPSET_SIZE) {
        break;
      }
    }
    Ipset.synchronize(
        entries,
        Ipset.HOST_NETWORK_PREFIX,
        Ipset.NamespacePrefix.R.name() + reputationType.toChar() + confidence.toChar() + '_' + identifier,
        setDir
    );
  }

  /**
   * See ip_reputation_sets-create.sql for set name encoding
   *
   * @see  #synchronizeIpset
   */
  private static void synchronizeNetworkIpset(
      java.util.Set networks,
      short networkPrefix,
      String identifier,
      PosixFile setDir
  ) throws IOException {
    java.util.Set entries = AoCollections.newLinkedHashSet(Math.min(Ipset.MAX_IPSET_SIZE + 1, networks.size()));
    for (Network network : networks) {
      entries.add(network.getNetwork());
      if (entries.size() > Ipset.MAX_IPSET_SIZE) {
        break;
      }
    }
    Ipset.synchronize(
        entries,
        networkPrefix,
        Ipset.NamespacePrefix.R.name() + Set.ReputationType.GOOD.toChar() + "N_" + identifier,
        setDir
    );
  }

  private static final Object rebuildLock = new Object();

  @Override
  @SuppressWarnings({"UseSpecificCatch", "TooBroadCatch"})
  protected boolean doRebuild() {
    try {
      AoservConnector conn = AoservDaemon.getConnector();
      Server thisServer = AoservDaemon.getThisServer();
      OperatingSystemVersion osv = thisServer.getHost().getOperatingSystemVersion();
      int osvId = osv.getPkey();
      if (
          // Only runs on Xen dom0 (firewalling done outside virtual servers)
          osvId != OperatingSystemVersion.CENTOS_5_DOM0_I686
              && osvId != OperatingSystemVersion.CENTOS_5_DOM0_X86_64
              && osvId != OperatingSystemVersion.CENTOS_7_DOM0_X86_64
      ) {
        throw new AssertionError("Unsupported OperatingSystemVersion: " + osv);
      }

      synchronized (rebuildLock) {
        final PosixFile ipreputationDir = getIpreputationDir();
        final Collection sets = conn.getNet().getReputation().getSet().getRows();

        // Track the names of each set, used to remove extra directories
        final java.util.Set setIdentifiers = AoCollections.newHashSet(sets.size());

        for (Set set : sets) {
          // Set settings
          final String identifier       = set.getIdentifier();
          final short maxUncertainGood  = set.getMaxUncertainReputation();
          final short minUncertainBad   = SafeMath.castShort(-maxUncertainGood);

          // Make sure to not remove the directory
          setIdentifiers.add(identifier);

          // Create the set directory if missing
          PosixFile setDir = new PosixFile(ipreputationDir, identifier, true);
          if (!setDir.getStat().exists()) {
            setDir.mkdir(false, 0700);
          }

          // TODO: Use concurrency equal to minimum of four or half the cores on the server

          // Split the IP addresses into four classes based on the set's settings.
          SortedSet    definiteBadHosts   = new TreeSet<>(badHostComparator);
          SortedSet    uncertainBadHosts  = new TreeSet<>(badHostComparator);
          SortedSet    uncertainGoodHosts = new TreeSet<>(goodHostComparator);
          SortedSet    definiteGoodHosts  = new TreeSet<>(goodHostComparator);
          for (Host host : set.getHosts()) {
            short rep = host.getReputation();
            if (rep < minUncertainBad) {
              definiteBadHosts.add(host);
            } else if (rep < 0) {
              uncertainBadHosts.add(host);
            } else if (rep > maxUncertainGood) {
              definiteGoodHosts.add(host);
            } else if (rep >= 0) {
              uncertainGoodHosts.add(host);
            } else {
              throw new AssertionError("rep=" + rep);
            }
          }

          // Sort the networks by reputation
          SortedSet goodNetworks = new TreeSet<>(goodNetworkComparator);
          goodNetworks.addAll(set.getNetworks());

          // Synchronize both the in-kernel set as well as the on-disk representations
          synchronizeHostIpset(
              definiteBadHosts,
              Set.ConfidenceType.DEFINITE,
              Set.ReputationType.BAD,
              identifier,
              setDir
          );
          synchronizeHostIpset(
              uncertainBadHosts,
              Set.ConfidenceType.UNCERTAIN,
              Set.ReputationType.BAD,
              identifier,
              setDir
          );
          synchronizeHostIpset(
              uncertainGoodHosts,
              Set.ConfidenceType.UNCERTAIN,
              Set.ReputationType.GOOD,
              identifier,
              setDir
          );
          synchronizeHostIpset(
              definiteGoodHosts,
              Set.ConfidenceType.DEFINITE,
              Set.ReputationType.GOOD,
              identifier,
              setDir
          );
          synchronizeNetworkIpset(
              goodNetworks,
              set.getNetworkPrefix(),
              identifier,
              setDir
          );
        }

        // TODO: Delete any sets that have reputation prefixes from the kernel
        //       as long as has zero references.  Don't delete files when still
        //       has references.

        // Delete any extra directories, after backing-up
        String[] list = ipreputationDir.list();
        if (list != null) {
          List deleteFileList = new ArrayList<>();
          for (String filename : list) {
            if (!setIdentifiers.contains(filename)) {
              PosixFile extraUf = new PosixFile(ipreputationDir, filename, true);
              deleteFileList.add(extraUf.getFile());
            }
          }
          BackupManager.backupAndDeleteFiles(deleteFileList);
        }
      }
      return true;
    } catch (ThreadDeath td) {
      throw td;
    } catch (Throwable t) {
      logger.log(Level.SEVERE, null, t);
      return false;
    }
  }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy