tor

The Tor anonymity network
git clone https://git.dasho.dev/tor.git
Log | Files | Refs | README | LICENSE

hs_dos.c (7977B)


      1 /* Copyright (c) 2019-2021, The Tor Project, Inc. */
      2 /* See LICENSE for licensing information */
      3 
      4 /**
      5 * \file hs_dos.c
      6 * \brief Implement denial of service mitigation for the onion service
      7 *        subsystem.
      8 *
      9 * This module defenses:
     10 *
     11 * - Introduction Rate Limiting: If enabled by the consensus, an introduction
     12 *   point will rate limit client introduction towards the service (INTRODUCE2
     13 *   cells). It uses a token bucket model with a rate and burst per second.
     14 *
     15 *   Proposal 305 will expand this module by allowing an operator to define
     16 *   these values into the ESTABLISH_INTRO cell. Not yet implemented.
     17 **/
     18 
     19 #define HS_DOS_PRIVATE
     20 
     21 #include "core/or/or.h"
     22 #include "app/config/config.h"
     23 
     24 #include "core/or/circuitlist.h"
     25 
     26 #include "feature/hs/hs_circuitmap.h"
     27 #include "feature/nodelist/networkstatus.h"
     28 #include "feature/relay/routermode.h"
     29 
     30 #include "lib/evloop/token_bucket.h"
     31 #include "lib/time/compat_time.h"
     32 
     33 #include "feature/hs/hs_dos.h"
     34 
     35 /** Default value of the allowed INTRODUCE2 cell rate per second. Above that
     36 * value per second, the introduction is denied. */
     37 #define HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC 25
     38 
     39 /** Default value of the allowed INTRODUCE2 cell burst per second. This is the
     40 * maximum value a token bucket has per second. We thus allow up to this value
     41 * of INTRODUCE2 cell per second but the bucket is refilled by the rate value
     42 * but never goes above that burst value. */
     43 #define HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC 200
     44 
     45 /** Default value of the consensus parameter enabling or disabling the
     46 * introduction DoS defense. Disabled by default. */
     47 #define HS_DOS_INTRODUCE_ENABLED_DEFAULT 0
     48 
     49 /** INTRODUCE2 rejected request counter. */
     50 static uint64_t intro2_rejected_count = 0;
     51 
     52 /* Consensus parameters. The ESTABLISH_INTRO DoS cell extension have higher
     53 * priority than these values. If no extension is sent, these are used only by
     54 * the introduction point. */
     55 static uint32_t consensus_param_introduce_rate_per_sec =
     56  HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC;
     57 static uint32_t consensus_param_introduce_burst_per_sec =
     58  HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC;
     59 static uint32_t consensus_param_introduce_defense_enabled =
     60  HS_DOS_INTRODUCE_ENABLED_DEFAULT;
     61 
     62 STATIC uint32_t
     63 get_intro2_enable_consensus_param(const networkstatus_t *ns)
     64 {
     65  return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSDefense",
     66                                 HS_DOS_INTRODUCE_ENABLED_DEFAULT, 0, 1);
     67 }
     68 
     69 /** Return the parameter for the introduction rate per sec. */
     70 STATIC uint32_t
     71 get_intro2_rate_consensus_param(const networkstatus_t *ns)
     72 {
     73  return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSRatePerSec",
     74                                 HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC,
     75                                 0, INT32_MAX);
     76 }
     77 
     78 /** Return the parameter for the introduction burst per sec. */
     79 STATIC uint32_t
     80 get_intro2_burst_consensus_param(const networkstatus_t *ns)
     81 {
     82  return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSBurstPerSec",
     83                                 HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC,
     84                                 0, INT32_MAX);
     85 }
     86 
     87 /** Go over all introduction circuit relay side and adjust their rate/burst
     88 * values using the global parameters. This is called right after the
     89 * consensus parameters might have changed. */
     90 static void
     91 update_intro_circuits(void)
     92 {
     93  /* Returns all HS version intro circuits. */
     94  smartlist_t *intro_circs = hs_circuitmap_get_all_intro_circ_relay_side();
     95 
     96  SMARTLIST_FOREACH_BEGIN(intro_circs, circuit_t *, circ) {
     97    /* Ignore circuit if the defenses were set explicitly through the
     98     * ESTABLISH_INTRO cell DoS extension. */
     99    if (TO_OR_CIRCUIT(circ)->introduce2_dos_defense_explicit) {
    100      continue;
    101    }
    102    /* Defenses might have been enabled or disabled. */
    103    TO_OR_CIRCUIT(circ)->introduce2_dos_defense_enabled =
    104      consensus_param_introduce_defense_enabled;
    105    /* Adjust the rate/burst value that might have changed. */
    106    token_bucket_ctr_adjust(&TO_OR_CIRCUIT(circ)->introduce2_bucket,
    107                            consensus_param_introduce_rate_per_sec,
    108                            consensus_param_introduce_burst_per_sec);
    109  } SMARTLIST_FOREACH_END(circ);
    110 
    111  smartlist_free(intro_circs);
    112 }
    113 
    114 /** Set consensus parameters. */
    115 static void
    116 set_consensus_parameters(const networkstatus_t *ns)
    117 {
    118  consensus_param_introduce_rate_per_sec =
    119    get_intro2_rate_consensus_param(ns);
    120  consensus_param_introduce_burst_per_sec =
    121    get_intro2_burst_consensus_param(ns);
    122  consensus_param_introduce_defense_enabled =
    123    get_intro2_enable_consensus_param(ns);
    124 
    125  /* The above might have changed which means we need to go through all
    126   * introduction circuits (relay side) and update the token buckets. */
    127  update_intro_circuits();
    128 }
    129 
    130 /*
    131 * Public API.
    132 */
    133 
    134 /** Initialize the INTRODUCE2 token bucket for the DoS defenses using the
    135 * consensus/default values. We might get a cell extension that changes those
    136 * later but if we don't, the default or consensus parameters are used. */
    137 void
    138 hs_dos_setup_default_intro2_defenses(or_circuit_t *circ)
    139 {
    140  tor_assert(circ);
    141 
    142  circ->introduce2_dos_defense_enabled =
    143    consensus_param_introduce_defense_enabled;
    144  token_bucket_ctr_init(&circ->introduce2_bucket,
    145                        consensus_param_introduce_rate_per_sec,
    146                        consensus_param_introduce_burst_per_sec,
    147                        (uint32_t) monotime_coarse_absolute_sec());
    148 }
    149 
    150 /** Called when the consensus has changed. We might have new consensus
    151 * parameters to look at. */
    152 void
    153 hs_dos_consensus_has_changed(const networkstatus_t *ns)
    154 {
    155  /* No point on updating these values if we are not a public relay that can
    156   * be picked to be an introduction point. */
    157  if (!public_server_mode(get_options())) {
    158    return;
    159  }
    160 
    161  set_consensus_parameters(ns);
    162 }
    163 
    164 /** Return true iff an INTRODUCE2 cell can be sent on the given service
    165 * introduction circuit. */
    166 bool
    167 hs_dos_can_send_intro2(or_circuit_t *s_intro_circ)
    168 {
    169  tor_assert(s_intro_circ);
    170 
    171  /* Allow to send the cell if the DoS defenses are disabled on the circuit.
    172   * This can be set by the consensus, the ESTABLISH_INTRO cell extension or
    173   * the hardcoded values in tor code. */
    174  if (!s_intro_circ->introduce2_dos_defense_enabled) {
    175    goto allow;
    176  }
    177 
    178  /* Should not happen but if so, scream loudly. */
    179  if (BUG(TO_CIRCUIT(s_intro_circ)->purpose != CIRCUIT_PURPOSE_INTRO_POINT)) {
    180    goto disallow;
    181  }
    182 
    183  /* This is called just after we got a valid and parsed INTRODUCE1 cell. The
    184   * service has been found and we have its introduction circuit.
    185   *
    186   * First, the INTRODUCE2 bucket will be refilled (if any). Then, decremented
    187   * because we are about to send or not the cell we just got. Finally,
    188   * evaluate if we can send it based on our token bucket state. */
    189 
    190  /* Refill INTRODUCE2 bucket. */
    191  token_bucket_ctr_refill(&s_intro_circ->introduce2_bucket,
    192                          (uint32_t) monotime_coarse_absolute_sec());
    193 
    194  /* Decrement the bucket for this valid INTRODUCE1 cell we just got. Don't
    195   * underflow else we end up with a too big of a bucket. */
    196  if (token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0) {
    197    token_bucket_ctr_dec(&s_intro_circ->introduce2_bucket, 1);
    198  }
    199 
    200  /* Finally, we can send a new INTRODUCE2 if there are still tokens. */
    201  if (token_bucket_ctr_get(&s_intro_circ->introduce2_bucket) > 0) {
    202    goto allow;
    203  }
    204 
    205  /* If we reach this point, then it means the bucket has reached zero, and
    206     we're going to disallow. */
    207 
    208 disallow:
    209  /* Increment stats counter, we are rejecting the INTRO2 cell. */
    210  intro2_rejected_count++;
    211  return false;
    212 
    213 allow:
    214  return true;
    215 }
    216 
    217 /** Return rolling count of rejected INTRO2. */
    218 uint64_t
    219 hs_dos_get_intro2_rejected_count(void)
    220 {
    221  return intro2_rejected_count;
    222 }
    223 
    224 /** Initialize the onion service Denial of Service subsystem. */
    225 void
    226 hs_dos_init(void)
    227 {
    228  set_consensus_parameters(NULL);
    229 }