/* Copyright (c) 2018, The Tor Project, Inc. */
/* See LICENSE for licensing information */

/*
 * \file dos.c
 * \brief Implement Denial of Service mitigation subsystem.
 */

#define DOS_PRIVATE

#include "or.h"
#include "channel.h"
#include "config.h"
#include "geoip.h"
#include "main.h"
#include "networkstatus.h"

#include "dos.h"

/*
 * Circuit creation denial of service mitigation.
 *
 * Namespace used for this mitigation framework is "dos_cc_" where "cc" is for
 * Circuit Creation.
 */

/* Is the circuit creation DoS mitigation enabled? */
static unsigned int dos_cc_enabled = 0;

/* Consensus parameters. They can be changed when a new consensus arrives.
 * They are initialized with the hardcoded default values. */
static uint32_t dos_cc_min_concurrent_conn;
static uint32_t dos_cc_circuit_rate_tenths;
static uint32_t dos_cc_circuit_burst;
static dos_cc_defense_type_t dos_cc_defense_type;
static int32_t dos_cc_defense_time_period;

/*
 * Concurrent connection denial of service mitigation.
 *
 * Namespace used for this mitigation framework is "dos_conn_".
 */

/* Is the connection DoS mitigation enabled? */
static unsigned int dos_conn_enabled = 0;

/* Consensus parameters. They can be changed when a new consensus arrives.
 * They are initialized with the hardcoded default values. */
static uint32_t dos_conn_max_concurrent_count;
static dos_conn_defense_type_t dos_conn_defense_type;

/*
 * General interface of the denial of service mitigation subsystem.
 */

/* Return true iff the circuit creation mitigation is enabled. We look at the
 * consensus for this else a default value is returned. */
MOCK_IMPL(STATIC unsigned int,
get_param_cc_enabled, (const networkstatus_t *ns))
{
  if (get_options()->DoSCircuitCreationEnabled != -1) {
    return get_options()->DoSCircuitCreationEnabled;
  }

  return !!networkstatus_get_param(ns, "DoSCircuitCreationEnabled",
                                   DOS_CC_ENABLED_DEFAULT, 0, 1);
}

/* Return the parameter for the minimum concurrent connection at which we'll
 * start counting circuit for a specific client address. */
STATIC uint32_t
get_param_cc_min_concurrent_connection(const networkstatus_t *ns)
{
  if (get_options()->DoSCircuitCreationMinConnections) {
    return get_options()->DoSCircuitCreationMinConnections;
  }
  return networkstatus_get_param(ns, "DoSCircuitCreationMinConnections",
                                 DOS_CC_MIN_CONCURRENT_CONN_DEFAULT,
                                 1, INT32_MAX);
}

/* Return the parameter for the time rate that is how many circuits over this
 * time span. */
static uint32_t
get_param_cc_circuit_rate_tenths(const networkstatus_t *ns)
{
  /* This is in seconds. */
  if (get_options()->DoSCircuitCreationRateTenths) {
    return get_options()->DoSCircuitCreationRateTenths;
  }
  return networkstatus_get_param(ns, "DoSCircuitCreationRateTenths",
                                 DOS_CC_CIRCUIT_RATE_TENTHS_DEFAULT,
                                 1, INT32_MAX);
}

/* Return the parameter for the maximum circuit count for the circuit time
 * rate. */
STATIC uint32_t
get_param_cc_circuit_burst(const networkstatus_t *ns)
{
  if (get_options()->DoSCircuitCreationBurst) {
    return get_options()->DoSCircuitCreationBurst;
  }
  return networkstatus_get_param(ns, "DoSCircuitCreationBurst",
                                 DOS_CC_CIRCUIT_BURST_DEFAULT,
                                 1, INT32_MAX);
}

/* Return the consensus parameter of the circuit creation defense type. */
static uint32_t
get_param_cc_defense_type(const networkstatus_t *ns)
{
  if (get_options()->DoSCircuitCreationDefenseType) {
    return get_options()->DoSCircuitCreationDefenseType;
  }
  return networkstatus_get_param(ns, "DoSCircuitCreationDefenseType",
                                 DOS_CC_DEFENSE_TYPE_DEFAULT,
                                 DOS_CC_DEFENSE_NONE, DOS_CC_DEFENSE_MAX);
}

/* Return the consensus parameter of the defense time period which is how much
 * time should we defend against a malicious client address. */
static int32_t
get_param_cc_defense_time_period(const networkstatus_t *ns)
{
  /* Time in seconds. */
  if (get_options()->DoSCircuitCreationDefenseTimePeriod) {
    return get_options()->DoSCircuitCreationDefenseTimePeriod;
  }
  return networkstatus_get_param(ns, "DoSCircuitCreationDefenseTimePeriod",
                                 DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT,
                                 0, INT32_MAX);
}

/* Return true iff connection mitigation is enabled. We look at the consensus
 * for this else a default value is returned. */
MOCK_IMPL(STATIC unsigned int,
get_param_conn_enabled, (const networkstatus_t *ns))
{
  if (get_options()->DoSConnectionEnabled != -1) {
    return get_options()->DoSConnectionEnabled;
  }
  return !!networkstatus_get_param(ns, "DoSConnectionEnabled",
                                   DOS_CONN_ENABLED_DEFAULT, 0, 1);
}

/* Return the consensus parameter for the maximum concurrent connection
 * allowed. */
STATIC uint32_t
get_param_conn_max_concurrent_count(const networkstatus_t *ns)
{
  if (get_options()->DoSConnectionMaxConcurrentCount) {
    return get_options()->DoSConnectionMaxConcurrentCount;
  }
  return networkstatus_get_param(ns, "DoSConnectionMaxConcurrentCount",
                                 DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT,
                                 1, INT32_MAX);
}

/* Return the consensus parameter of the connection defense type. */
static uint32_t
get_param_conn_defense_type(const networkstatus_t *ns)
{
  if (get_options()->DoSConnectionDefenseType) {
    return get_options()->DoSConnectionDefenseType;
  }
  return networkstatus_get_param(ns, "DoSConnectionDefenseType",
                                 DOS_CONN_DEFENSE_TYPE_DEFAULT,
                                 DOS_CONN_DEFENSE_NONE, DOS_CONN_DEFENSE_MAX);
}

/* Set circuit creation parameters located in the consensus or their default
 * if none are present. Called at initialization or when the consensus
 * changes. */
static void
set_dos_parameters(const networkstatus_t *ns)
{
  /* Get the default consensus param values. */
  dos_cc_enabled = get_param_cc_enabled(ns);
  dos_cc_min_concurrent_conn = get_param_cc_min_concurrent_connection(ns);
  dos_cc_circuit_rate_tenths = get_param_cc_circuit_rate_tenths(ns);
  dos_cc_circuit_burst = get_param_cc_circuit_burst(ns);
  dos_cc_defense_time_period = get_param_cc_defense_time_period(ns);
  dos_cc_defense_type = get_param_cc_defense_type(ns);

  /* Connection detection. */
  dos_conn_enabled = get_param_conn_enabled(ns);
  dos_conn_max_concurrent_count = get_param_conn_max_concurrent_count(ns);
  dos_conn_defense_type = get_param_conn_defense_type(ns);
}

/* Free everything for the circuit creation DoS mitigation subsystem. */
static void
cc_free_all(void)
{
  /* If everything is freed, the circuit creation subsystem is not enabled. */
  dos_cc_enabled = 0;
}

/* Called when the consensus has changed. Do appropriate actions for the
 * circuit creation subsystem. */
static void
cc_consensus_has_changed(const networkstatus_t *ns)
{
  /* Looking at the consensus, is the circuit creation subsystem enabled? If
   * not and it was enabled before, clean it up. */
  if (dos_cc_enabled && !get_param_cc_enabled(ns)) {
    cc_free_all();
  }
}

/* Concurrent connection private API. */

/* Free everything for the connection DoS mitigation subsystem. */
static void
conn_free_all(void)
{
  dos_conn_enabled = 0;
}

/* Called when the consensus has changed. Do appropriate actions for the
 * connection mitigation subsystem. */
static void
conn_consensus_has_changed(const networkstatus_t *ns)
{
  /* Looking at the consensus, is the connection mitigation subsystem enabled?
   * If not and it was enabled before, clean it up. */
  if (dos_conn_enabled && !get_param_conn_enabled(ns)) {
    conn_free_all();
  }
}

/* General private API */

/* Return true iff we have at least one DoS detection enabled. This is used to
 * decide if we need to allocate any kind of high level DoS object. */
static inline int
dos_is_enabled(void)
{
  return (dos_cc_enabled || dos_conn_enabled);
}

/* Circuit creation public API. */

/* Concurrent connection detection public API. */

/* General API */

/* Called when the consensus has changed. We might have new consensus
 * parameters to look at. */
void
dos_consensus_has_changed(const networkstatus_t *ns)
{
  cc_consensus_has_changed(ns);
  conn_consensus_has_changed(ns);

  /* We were already enabled or we just became enabled but either way, set the
   * consensus parameters for all subsystems. */
  set_dos_parameters(ns);
}

/* Return true iff the DoS mitigation subsystem is enabled. */
int
dos_enabled(void)
{
  return dos_is_enabled();
}

/* Free everything from the Denial of Service subsystem. */
void
dos_free_all(void)
{
  /* Free the circuit creation mitigation subsystem. It is safe to do this
   * even if it wasn't initialized. */
  cc_free_all();

  /* Free the connection mitigation subsystem. It is safe to do this even if
   * it wasn't initialized. */
  conn_free_all();
}

/* Initialize the Denial of Service subsystem. */
void
dos_init(void)
{
  /* To initialize, we only need to get the parameters. */
  set_dos_parameters(NULL);
}

