Browse Source

dos: Initial code of Denial of Service mitigation

This commit introduces the src/or/dos.{c|h} files that contains the code for
the Denial of Service mitigation subsystem. It currently contains basic
functions to initialize and free the subsystem. They are used at this commit.

The torrc options and consensus parameters are defined at this commit and
getters are implemented.

Signed-off-by: David Goulet <dgoulet@torproject.org>
David Goulet 6 years ago
parent
commit
64149353dd
9 changed files with 483 additions and 4 deletions
  1. 1 1
      src/common/log.c
  2. 3 1
      src/common/torlog.h
  3. 25 0
      src/or/config.c
  4. 289 0
      src/or/dos.c
  5. 120 0
      src/or/dos.h
  6. 2 0
      src/or/include.am
  7. 2 0
      src/or/main.c
  8. 11 2
      src/or/networkstatus.c
  9. 30 0
      src/or/or.h

+ 1 - 1
src/common/log.c

@@ -1177,7 +1177,7 @@ static const char *domain_list[] = {
   "GENERAL", "CRYPTO", "NET", "CONFIG", "FS", "PROTOCOL", "MM",
   "HTTP", "APP", "CONTROL", "CIRC", "REND", "BUG", "DIR", "DIRSERV",
   "OR", "EDGE", "ACCT", "HIST", "HANDSHAKE", "HEARTBEAT", "CHANNEL",
-  "SCHED", NULL
+  "SCHED", "DOS", NULL
 };
 
 /** Return a bitmask for the log domain for which <b>domain</b> is the name,

+ 3 - 1
src/common/torlog.h

@@ -99,8 +99,10 @@
 #define LD_CHANNEL   (1u<<21)
 /** Scheduler */
 #define LD_SCHED     (1u<<22)
+/** Denial of Service mitigation. */
+#define LD_DOS       (1u<<23)
 /** Number of logging domains in the code. */
-#define N_LOGGING_DOMAINS 23
+#define N_LOGGING_DOMAINS 24
 
 /** This log message is not safe to send to a callback-based logger
  * immediately.  Used as a flag, not a log domain. */

+ 25 - 0
src/or/config.c

@@ -29,6 +29,7 @@
 #include "dirserv.h"
 #include "dirvote.h"
 #include "dns.h"
+#include "dos.h"
 #include "entrynodes.h"
 #include "geoip.h"
 #include "hibernate.h"
@@ -241,6 +242,19 @@ static config_var_t option_vars_[] = {
   OBSOLETE("DynamicDHGroups"),
   VPORT(DNSPort,                     LINELIST, NULL),
   V(DNSListenAddress,            LINELIST, NULL),
+  /* DoS circuit creation options. */
+  V(DoSCircuitCreationEnabled,   AUTOBOOL, "auto"),
+  V(DoSCircuitCreationMinConnections,      UINT, "0"),
+  V(DoSCircuitCreationRateTenths,          UINT, "0"),
+  V(DoSCircuitCreationBurst,     UINT,     "0"),
+  V(DoSCircuitCreationDefenseType,         INT,  "0"),
+  V(DoSCircuitCreationDefenseTimePeriod,   INTERVAL, "0"),
+  /* DoS connection options. */
+  V(DoSConnectionEnabled,        AUTOBOOL, "auto"),
+  V(DoSConnectionMaxConcurrentCount,       UINT, "0"),
+  V(DoSConnectionDefenseType,    INT,      "0"),
+  /* DoS single hop client options. */
+  V(DoSRefuseSingleHopClientRendezvous,    AUTOBOOL, "auto"),
   V(DownloadExtraInfo,           BOOL,     "0"),
   V(TestingEnableConnBwEvent,    BOOL,     "0"),
   V(TestingEnableCellStatsEvent, BOOL,     "0"),
@@ -2039,6 +2053,17 @@ options_act(const or_options_t *old_options)
     }
   }
 
+  /* DoS mitigation subsystem only applies to public relay. */
+  if (public_server_mode(options)) {
+    /* If we are configured as a relay, initialize the subsystem. Even on HUP,
+     * this is safe to call as it will load data from the current options
+     * or/and the consensus. */
+    dos_init();
+  } else if (old_options && public_server_mode(old_options)) {
+    /* Going from relay to non relay, clean it up. */
+    dos_free_all();
+  }
+
   /* Load the webpage we're going to serve every time someone asks for '/' on
      our DirPort. */
   tor_free(global_dirfrontpagecontents);

+ 289 - 0
src/or/dos.c

@@ -0,0 +1,289 @@
+/* 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);
+}
+

+ 120 - 0
src/or/dos.h

@@ -0,0 +1,120 @@
+/* Copyright (c) 2018, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+/*
+ * \file dos.h
+ * \brief Header file for dos.c
+ */
+
+#ifndef TOR_DOS_H
+#define TOR_DOS_H
+
+/* Structure that keeps stats of client connection per-IP. */
+typedef struct cc_client_stats_t {
+  /* Number of allocated circuits remaining for this address.  It is
+   * decremented every time a new circuit is seen for this client address and
+   * if the count goes to 0, we have a positive detection. */
+  uint32_t circuit_bucket;
+
+  /* When was the last time we've refilled the circuit bucket? This is used to
+   * know if we need to refill the bucket when a new circuit is seen. It is
+   * synchronized using approx_time(). */
+  time_t last_circ_bucket_refill_ts;
+
+  /* This client address was detected to be above the circuit creation rate
+   * and this timestamp indicates until when it should remain marked as
+   * detected so we can apply a defense for the address. It is synchronized
+   * using the approx_time(). */
+  time_t marked_until_ts;
+} cc_client_stats_t;
+
+/* This object is a top level object that contains everything related to the
+ * per-IP client DoS mitigation. Because it is per-IP, it is used in the geoip
+ * clientmap_entry_t object. */
+typedef struct dos_client_stats_t {
+  /* Concurrent connection count from the specific address. 2^32 is most
+   * likely way too big for the amount of allowed file descriptors. */
+  uint32_t concurrent_count;
+
+  /* Circuit creation statistics. This is only used if the circuit creation
+   * subsystem has been enabled (dos_cc_enabled). */
+  cc_client_stats_t cc_stats;
+} dos_client_stats_t;
+
+/* General API. */
+
+void dos_init(void);
+void dos_free_all(void);
+void dos_consensus_has_changed(const networkstatus_t *ns);
+int dos_enabled(void);
+
+/*
+ * Circuit creation DoS mitigation subsystemn interface.
+ */
+
+/* DoSCircuitCreationEnabled default. Disabled by default. */
+#define DOS_CC_ENABLED_DEFAULT 0
+/* DoSCircuitCreationDefenseType maps to the dos_cc_defense_type_t enum. */
+#define DOS_CC_DEFENSE_TYPE_DEFAULT DOS_CC_DEFENSE_REFUSE_CELL
+/* DoSCircuitCreationMinConnections default */
+#define DOS_CC_MIN_CONCURRENT_CONN_DEFAULT 3
+/* DoSCircuitCreationRateTenths is 3 per seconds. */
+#define DOS_CC_CIRCUIT_RATE_TENTHS_DEFAULT (3 * 10)
+/* DoSCircuitCreationBurst default. */
+#define DOS_CC_CIRCUIT_BURST_DEFAULT 90
+/* DoSCircuitCreationDefenseTimePeriod in seconds. */
+#define DOS_CC_DEFENSE_TIME_PERIOD_DEFAULT (60 * 60)
+
+/* Type of defense that we can use for the circuit creation DoS mitigation. */
+typedef enum dos_cc_defense_type_t {
+  /* No defense used. */
+  DOS_CC_DEFENSE_NONE             = 1,
+  /* Refuse any cells which means a DESTROY cell will be sent back. */
+  DOS_CC_DEFENSE_REFUSE_CELL      = 2,
+
+  /* Maximum value that can be used. Useful for the boundaries of the
+   * consensus parameter. */
+  DOS_CC_DEFENSE_MAX              = 2,
+} dos_cc_defense_type_t;
+
+/*
+ * Concurrent connection DoS mitigation interface.
+ */
+
+/* DoSConnectionEnabled default. Disabled by default. */
+#define DOS_CONN_ENABLED_DEFAULT 0
+/* DoSConnectionMaxConcurrentCount default. */
+#define DOS_CONN_MAX_CONCURRENT_COUNT_DEFAULT 100
+/* DoSConnectionDefenseType maps to the dos_conn_defense_type_t enum. */
+#define DOS_CONN_DEFENSE_TYPE_DEFAULT DOS_CONN_DEFENSE_CLOSE
+
+/* Type of defense that we can use for the concurrent connection DoS
+ * mitigation. */
+typedef enum dos_conn_defense_type_t {
+  /* No defense used. */
+  DOS_CONN_DEFENSE_NONE             = 1,
+  /* Close immediately the connection meaning refuse it. */
+  DOS_CONN_DEFENSE_CLOSE            = 2,
+
+  /* Maximum value that can be used. Useful for the boundaries of the
+   * consensus parameter. */
+  DOS_CONN_DEFENSE_MAX              = 2,
+} dos_conn_defense_type_t;
+
+#ifdef DOS_PRIVATE
+
+STATIC uint32_t get_param_conn_max_concurrent_count(
+                                              const networkstatus_t *ns);
+STATIC uint32_t get_param_cc_circuit_burst(const networkstatus_t *ns);
+STATIC uint32_t get_param_cc_min_concurrent_connection(
+                                            const networkstatus_t *ns);
+
+MOCK_DECL(STATIC unsigned int, get_param_cc_enabled,
+          (const networkstatus_t *ns));
+MOCK_DECL(STATIC unsigned int, get_param_conn_enabled,
+          (const networkstatus_t *ns));
+
+#endif /* TOR_DOS_PRIVATE */
+
+#endif /* TOR_DOS_H */
+

+ 2 - 0
src/or/include.am

@@ -43,6 +43,7 @@ LIBTOR_A_SOURCES = \
 	src/or/dirvote.c				\
 	src/or/dns.c					\
 	src/or/dnsserv.c				\
+	src/or/dos.c					\
 	src/or/fp_pair.c				\
 	src/or/geoip.c					\
 	src/or/entrynodes.c				\
@@ -151,6 +152,7 @@ ORHEADERS = \
 	src/or/dns.h					\
 	src/or/dns_structs.h				\
 	src/or/dnsserv.h				\
+	src/or/dos.h					\
 	src/or/ext_orport.h				\
 	src/or/fallback_dirs.inc			\
 	src/or/fp_pair.h				\

+ 2 - 0
src/or/main.c

@@ -34,6 +34,7 @@
 #include "dirvote.h"
 #include "dns.h"
 #include "dnsserv.h"
+#include "dos.h"
 #include "entrynodes.h"
 #include "geoip.h"
 #include "hibernate.h"
@@ -2989,6 +2990,7 @@ tor_free_all(int postfork)
   control_free_all();
   sandbox_free_getaddrinfo_cache();
   protover_free_all();
+  dos_free_all();
   if (!postfork) {
     config_free_all();
     or_state_free_all();

+ 11 - 2
src/or/networkstatus.c

@@ -23,6 +23,7 @@
 #include "directory.h"
 #include "dirserv.h"
 #include "dirvote.h"
+#include "dos.h"
 #include "entrynodes.h"
 #include "main.h"
 #include "microdesc.h"
@@ -1502,6 +1503,15 @@ notify_control_networkstatus_changed(const networkstatus_t *old_c,
   smartlist_free(changed);
 }
 
+/* Called when the consensus has changed from old_c to new_c. */
+static void
+notify_networkstatus_changed(const networkstatus_t *old_c,
+                             const networkstatus_t *new_c)
+{
+  notify_control_networkstatus_changed(old_c, new_c);
+  dos_consensus_has_changed(new_c);
+}
+
 /** Copy all the ancillary information (like router download status and so on)
  * from <b>old_c</b> to <b>new_c</b>. */
 static void
@@ -1826,8 +1836,7 @@ networkstatus_set_current_consensus(const char *consensus,
   const int is_usable_flavor = flav == usable_consensus_flavor();
 
   if (is_usable_flavor) {
-    notify_control_networkstatus_changed(
-                         networkstatus_get_latest_consensus(), c);
+    notify_networkstatus_changed(networkstatus_get_latest_consensus(), c);
   }
   if (flav == FLAV_NS) {
     if (current_ns_consensus) {

+ 30 - 0
src/or/or.h

@@ -4510,6 +4510,36 @@ typedef struct {
 
   /** If 1, we skip all OOS checks. */
   int DisableOOSCheck;
+
+  /** Autobool: Is the circuit creation DoS mitigation subsystem enabled? */
+  int DoSCircuitCreationEnabled;
+  /** Minimum concurrent connection needed from one single address before any
+   * defense is used. */
+  int DoSCircuitCreationMinConnections;
+  /** Circuit rate, in tenths of a second, that is used to refill the token
+   * bucket at this given rate. */
+  int DoSCircuitCreationRateTenths;
+  /** Maximum allowed burst of circuits. Reaching that value, the address is
+   * detected as malicious and a defense might be used. */
+  int DoSCircuitCreationBurst;
+  /** When an address is marked as malicous, what defense should be used
+   * against it. See the dos_cc_defense_type_t enum. */
+  int DoSCircuitCreationDefenseType;
+  /** For how much time (in seconds) the defense is applicable for a malicious
+   * address. A random time delta is added to the defense time of an address
+   * which will be between 1 second and half of this value. */
+  int DoSCircuitCreationDefenseTimePeriod;
+
+  /** Autobool: Is the DoS connection mitigation subsystem enabled? */
+  int DoSConnectionEnabled;
+  /** Maximum concurrent connection allowed per address. */
+  int DoSConnectionMaxConcurrentCount;
+  /** When an address is reaches the maximum count, what defense should be
+   * used against it. See the dos_conn_defense_type_t enum. */
+  int DoSConnectionDefenseType;
+
+  /** Autobool: Do we refuse single hop client rendezvous? */
+  int DoSRefuseSingleHopClientRendezvous;
 } or_options_t;
 
 /** Persistent state for an onion router, as saved to disk. */