diff options
Diffstat (limited to 'src/feature')
-rw-r--r-- | src/feature/hs/hs_cell.c | 111 | ||||
-rw-r--r-- | src/feature/hs/hs_cell.h | 11 | ||||
-rw-r--r-- | src/feature/hs/hs_circuit.c | 2 | ||||
-rw-r--r-- | src/feature/hs/hs_config.c | 60 | ||||
-rw-r--r-- | src/feature/hs/hs_config.h | 9 | ||||
-rw-r--r-- | src/feature/hs/hs_dos.c | 68 | ||||
-rw-r--r-- | src/feature/hs/hs_dos.h | 10 | ||||
-rw-r--r-- | src/feature/hs/hs_intropoint.c | 190 | ||||
-rw-r--r-- | src/feature/hs/hs_intropoint.h | 3 | ||||
-rw-r--r-- | src/feature/hs/hs_service.c | 7 | ||||
-rw-r--r-- | src/feature/hs/hs_service.h | 9 | ||||
-rw-r--r-- | src/feature/nodelist/nodelist.c | 13 | ||||
-rw-r--r-- | src/feature/nodelist/nodelist.h | 1 | ||||
-rw-r--r-- | src/feature/rend/rendmid.c | 3 |
14 files changed, 454 insertions, 43 deletions
diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 69f1ccbef4..547dda3e16 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -473,10 +473,110 @@ introduce1_set_legacy_id(trn_cell_introduce1_t *cell, } } +/* Build and add to the given DoS cell extension the given parameter type and + * value. */ +static void +build_establish_intro_dos_param(trn_cell_extension_dos_t *dos_ext, + uint8_t param_type, uint64_t param_value) +{ + trn_cell_extension_dos_param_t *dos_param = + trn_cell_extension_dos_param_new(); + + /* Extra safety. We should never send an unknown parameter type. */ + tor_assert(param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC || + param_type == TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC); + + trn_cell_extension_dos_param_set_type(dos_param, param_type); + trn_cell_extension_dos_param_set_value(dos_param, param_value); + trn_cell_extension_dos_add_params(dos_ext, dos_param); + + /* Not freeing the trunnel object because it is now owned by dos_ext. */ +} + +/* Build the DoS defense cell extension and put it in the given extensions + * object. This can't fail. */ +static void +build_establish_intro_dos_extension(const hs_service_config_t *service_config, + trn_cell_extension_t *extensions) +{ + ssize_t ret, dos_ext_encoded_len; + uint8_t *field_array; + trn_cell_extension_field_t *field; + trn_cell_extension_dos_t *dos_ext; + + tor_assert(service_config); + tor_assert(extensions); + + /* We are creating a cell extension field of the type DoS. */ + field = trn_cell_extension_field_new(); + trn_cell_extension_field_set_field_type(field, + TRUNNEL_CELL_EXTENSION_TYPE_DOS); + + /* Build DoS extension field. We will put in two parameters. */ + dos_ext = trn_cell_extension_dos_new(); + trn_cell_extension_dos_set_n_params(dos_ext, 2); + + /* Build DoS parameter INTRO2 rate per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC, + service_config->intro_dos_rate_per_sec); + /* Build DoS parameter INTRO2 burst per second. */ + build_establish_intro_dos_param(dos_ext, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC, + service_config->intro_dos_burst_per_sec); + + /* Set the field with the encoded DoS extension. */ + dos_ext_encoded_len = trn_cell_extension_dos_encoded_len(dos_ext); + /* Set length field and the field array size length. */ + trn_cell_extension_field_set_field_len(field, dos_ext_encoded_len); + trn_cell_extension_field_setlen_field(field, dos_ext_encoded_len); + /* Encode the DoS extension into the cell extension field. */ + field_array = trn_cell_extension_field_getarray_field(field); + ret = trn_cell_extension_dos_encode(field_array, + trn_cell_extension_field_getlen_field(field), dos_ext); + tor_assert(ret == dos_ext_encoded_len); + + /* Finally, encode field into the cell extension. */ + trn_cell_extension_add_fields(extensions, field); + + /* We've just add an extension field to the cell extensions so increment the + * total number. */ + trn_cell_extension_set_num(extensions, + trn_cell_extension_get_num(extensions) + 1); + + /* Cleanup. DoS extension has been encoded at this point. */ + trn_cell_extension_dos_free(dos_ext); +} + /* ========== */ /* Public API */ /* ========== */ +/* Allocate and build all the ESTABLISH_INTRO cell extension. The given + * extensions pointer is always set to a valid cell extension object. */ +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip) +{ + trn_cell_extension_t *extensions; + + tor_assert(service_config); + tor_assert(ip); + + extensions = trn_cell_extension_new(); + trn_cell_extension_set_num(extensions, 0); + + /* If the defense has been enabled service side (by the operator with a + * torrc option) and the intro point does support it. */ + if (service_config->has_dos_defense_enabled && + ip->support_intro2_dos_defense) { + /* This function takes care to increment the number of extensions. */ + build_establish_intro_dos_extension(service_config, extensions); + } + + return extensions; +} + /* Build an ESTABLISH_INTRO cell with the given circuit nonce and intro point * object. The encoded cell is put in cell_out that MUST at least be of the * size of RELAY_PAYLOAD_SIZE. Return the encoded cell length on success else @@ -484,15 +584,17 @@ introduce1_set_legacy_id(trn_cell_introduce1_t *cell, * legacy cell creation. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *service_config, const hs_service_intro_point_t *ip, uint8_t *cell_out) { ssize_t cell_len = -1; uint16_t sig_len = ED25519_SIG_LEN; - trn_cell_extension_t *ext; trn_cell_establish_intro_t *cell = NULL; + trn_cell_extension_t *extensions; tor_assert(circ_nonce); + tor_assert(service_config); tor_assert(ip); /* Quickly handle the legacy IP. */ @@ -505,11 +607,12 @@ hs_cell_build_establish_intro(const char *circ_nonce, goto done; } + /* Build the extensions, if any. */ + extensions = build_establish_intro_extensions(service_config, ip); + /* Set extension data. None used here. */ - ext = trn_cell_extension_new(); - trn_cell_extension_set_num(ext, 0); cell = trn_cell_establish_intro_new(); - trn_cell_establish_intro_set_extensions(cell, ext); + trn_cell_establish_intro_set_extensions(cell, extensions); /* Set signature size. Array is then allocated in the cell. We need to do * this early so we can use trunnel API to get the signature length. */ trn_cell_establish_intro_set_sig_len(cell, sig_len); diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h index 9569de535e..864b6fda5f 100644 --- a/src/feature/hs/hs_cell.h +++ b/src/feature/hs/hs_cell.h @@ -79,6 +79,7 @@ typedef struct hs_cell_introduce2_data_t { /* Build cell API. */ ssize_t hs_cell_build_establish_intro(const char *circ_nonce, + const hs_service_config_t *config, const hs_service_intro_point_t *ip, uint8_t *cell_out); ssize_t hs_cell_build_rendezvous1(const uint8_t *rendezvous_cookie, @@ -105,5 +106,15 @@ int hs_cell_parse_rendezvous2(const uint8_t *payload, size_t payload_len, /* Util API. */ void hs_cell_introduce1_data_clear(hs_cell_introduce1_data_t *data); +#ifdef TOR_UNIT_TESTS + +#include "trunnel/hs/cell_common.h" + +STATIC trn_cell_extension_t * +build_establish_intro_extensions(const hs_service_config_t *service_config, + const hs_service_intro_point_t *ip); + +#endif /* defined(TOR_UNIT_TESTS) */ + #endif /* !defined(TOR_HS_CELL_H) */ diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index a5aa2fb228..5e213b5aba 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -317,7 +317,7 @@ send_establish_intro(const hs_service_t *service, /* Encode establish intro cell. */ cell_len = hs_cell_build_establish_intro(circ->cpath->prev->rend_circ_nonce, - ip, payload); + &service->config, ip, payload); if (cell_len < 0) { log_warn(LD_REND, "Unable to encode ESTABLISH_INTRO cell for service %s " "on circuit %u. Closing circuit.", diff --git a/src/feature/hs/hs_config.c b/src/feature/hs/hs_config.c index 87f6257591..7424d7d3ce 100644 --- a/src/feature/hs/hs_config.c +++ b/src/feature/hs/hs_config.c @@ -218,6 +218,9 @@ config_has_invalid_options(const config_line_t *line_, const char *opts_exclude_v2[] = { "HiddenServiceExportCircuitID", + "HiddenServiceEnableIntroDoSDefense", + "HiddenServiceEnableIntroDoSRatePerSec", + "HiddenServiceEnableIntroDoSBurstPerSec", NULL /* End marker. */ }; @@ -276,6 +279,15 @@ config_validate_service(const hs_service_config_t *config) goto invalid; } + /* DoS validation values. */ + if (config->has_dos_defense_enabled && + (config->intro_dos_burst_per_sec < config->intro_dos_rate_per_sec)) { + log_warn(LD_CONFIG, "Hidden service DoS defenses burst (%" PRIu32 ") can " + "not be smaller than the rate value (%" PRIu32 ").", + config->intro_dos_burst_per_sec, config->intro_dos_rate_per_sec); + goto invalid; + } + /* Valid. */ return 0; invalid: @@ -296,6 +308,8 @@ config_service_v3(const config_line_t *line_, { int have_num_ip = 0; bool export_circuit_id = false; /* just to detect duplicate options */ + bool dos_enabled = false, dos_rate_per_sec = false; + bool dos_burst_per_sec = false; const char *dup_opt_seen = NULL; const config_line_t *line; @@ -334,6 +348,52 @@ config_service_v3(const config_line_t *line_, export_circuit_id = true; continue; } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSDefense")) { + config->has_dos_defense_enabled = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT, + 1, &ok); + if (!ok || dos_enabled) { + if (dos_enabled) { + dup_opt_seen = line->key; + } + goto err; + } + dos_enabled = true; + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSRatePerSec")) { + config->intro_dos_rate_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX, &ok); + if (!ok || dos_rate_per_sec) { + if (dos_rate_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_rate_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses rate set to: %" PRIu32, + config->intro_dos_rate_per_sec); + continue; + } + if (!strcasecmp(line->key, "HiddenServiceEnableIntroDoSBurstPerSec")) { + config->intro_dos_burst_per_sec = + (unsigned int) helper_parse_uint64(line->key, line->value, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX, &ok); + if (!ok || dos_burst_per_sec) { + if (dos_burst_per_sec) { + dup_opt_seen = line->key; + } + goto err; + } + dos_burst_per_sec = true; + log_info(LD_REND, "Service INTRO2 DoS defenses burst set to: %" PRIu32, + config->intro_dos_burst_per_sec); + continue; + } } /* We do not load the key material for the service at this stage. This is diff --git a/src/feature/hs/hs_config.h b/src/feature/hs/hs_config.h index 040e451f13..beefc7a613 100644 --- a/src/feature/hs/hs_config.h +++ b/src/feature/hs/hs_config.h @@ -15,6 +15,15 @@ #define HS_CONFIG_MAX_STREAMS_PER_RDV_CIRCUIT 65535 /* Maximum number of intro points per version 3 services. */ #define HS_CONFIG_V3_MAX_INTRO_POINTS 20 +/* Default value for the introduction DoS defenses. The MIN/MAX are inclusive + * meaning they can be used as valid values. */ +#define HS_CONFIG_V3_DOS_DEFENSE_DEFAULT 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT 25 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX INT32_MAX +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT 200 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN 0 +#define HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX INT32_MAX /* API */ diff --git a/src/feature/hs/hs_dos.c b/src/feature/hs/hs_dos.c index a4586dd700..0ae36017f8 100644 --- a/src/feature/hs/hs_dos.c +++ b/src/feature/hs/hs_dos.c @@ -45,24 +45,26 @@ * introduction DoS defense. Disabled by default. */ #define HS_DOS_INTRODUCE_ENABLED_DEFAULT 0 -/* Consensus parameters. */ -static uint32_t hs_dos_introduce_rate_per_sec = +/* Consensus parameters. The ESTABLISH_INTRO DoS cell extension have higher + * priority than these values. If no extension is sent, these are used only by + * the introduction point. */ +static uint32_t consensus_param_introduce_rate_per_sec = HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC; -static uint32_t hs_dos_introduce_burst_per_sec = +static uint32_t consensus_param_introduce_burst_per_sec = HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC; -static uint32_t hs_dos_introduce_enabled = +static uint32_t consensus_param_introduce_defense_enabled = HS_DOS_INTRODUCE_ENABLED_DEFAULT; -static uint32_t -get_param_intro_dos_enabled(const networkstatus_t *ns) +STATIC uint32_t +get_intro2_enable_consensus_param(const networkstatus_t *ns) { return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSDefense", HS_DOS_INTRODUCE_ENABLED_DEFAULT, 0, 1); } /* Return the parameter for the introduction rate per sec. */ -static uint32_t -get_param_rate_per_sec(const networkstatus_t *ns) +STATIC uint32_t +get_intro2_rate_consensus_param(const networkstatus_t *ns) { return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSRatePerSec", HS_DOS_INTRODUCE_DEFAULT_CELL_RATE_PER_SEC, @@ -70,8 +72,8 @@ get_param_rate_per_sec(const networkstatus_t *ns) } /* Return the parameter for the introduction burst per sec. */ -static uint32_t -get_param_burst_per_sec(const networkstatus_t *ns) +STATIC uint32_t +get_intro2_burst_consensus_param(const networkstatus_t *ns) { return networkstatus_get_param(ns, "HiddenServiceEnableIntroDoSBurstPerSec", HS_DOS_INTRODUCE_DEFAULT_CELL_BURST_PER_SEC, @@ -88,10 +90,13 @@ update_intro_circuits(void) smartlist_t *intro_circs = hs_circuitmap_get_all_intro_circ_relay_side(); SMARTLIST_FOREACH_BEGIN(intro_circs, circuit_t *, circ) { + /* Defenses might have been enabled or disabled. */ + TO_OR_CIRCUIT(circ)->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; /* Adjust the rate/burst value that might have changed. */ token_bucket_ctr_adjust(&TO_OR_CIRCUIT(circ)->introduce2_bucket, - hs_dos_get_intro2_rate(), - hs_dos_get_intro2_burst()); + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec); } SMARTLIST_FOREACH_END(circ); smartlist_free(intro_circs); @@ -101,9 +106,12 @@ update_intro_circuits(void) static void set_consensus_parameters(const networkstatus_t *ns) { - hs_dos_introduce_rate_per_sec = get_param_rate_per_sec(ns); - hs_dos_introduce_burst_per_sec = get_param_burst_per_sec(ns); - hs_dos_introduce_enabled = get_param_intro_dos_enabled(ns); + consensus_param_introduce_rate_per_sec = + get_intro2_rate_consensus_param(ns); + consensus_param_introduce_burst_per_sec = + get_intro2_burst_consensus_param(ns); + consensus_param_introduce_defense_enabled = + get_intro2_enable_consensus_param(ns); /* The above might have changed which means we need to go through all * introduction circuits (relay side) and update the token buckets. */ @@ -114,18 +122,20 @@ set_consensus_parameters(const networkstatus_t *ns) * Public API. */ -/* Return the INTRODUCE2 cell rate per second. */ -uint32_t -hs_dos_get_intro2_rate(void) -{ - return hs_dos_introduce_rate_per_sec; -} - -/* Return the INTRODUCE2 cell burst per second. */ -uint32_t -hs_dos_get_intro2_burst(void) +/* Initialize the INTRODUCE2 token bucket for the DoS defenses using the + * consensus/default values. We might get a cell extension that changes those + * later but if we don't, the default or consensus parameters are used. */ +void +hs_dos_setup_default_intro2_defenses(or_circuit_t *circ) { - return hs_dos_introduce_burst_per_sec; + tor_assert(circ); + + circ->introduce2_dos_defense_enabled = + consensus_param_introduce_defense_enabled; + token_bucket_ctr_init(&circ->introduce2_bucket, + consensus_param_introduce_rate_per_sec, + consensus_param_introduce_burst_per_sec, + (uint32_t) approx_time()); } /* Called when the consensus has changed. We might have new consensus @@ -149,8 +159,10 @@ hs_dos_can_send_intro2(or_circuit_t *s_intro_circ) { tor_assert(s_intro_circ); - /* Always allowed if the defense is disabled. */ - if (!hs_dos_introduce_enabled) { + /* Allow to send the cell if the DoS defenses are disabled on the circuit. + * This can be set by the consensus, the ESTABLISH_INTRO cell extension or + * the hardcoded values in tor code. */ + if (!s_intro_circ->introduce2_dos_defense_enabled) { return true; } diff --git a/src/feature/hs/hs_dos.h b/src/feature/hs/hs_dos.h index 9fba00b52b..6647b24be0 100644 --- a/src/feature/hs/hs_dos.h +++ b/src/feature/hs/hs_dos.h @@ -20,16 +20,18 @@ void hs_dos_init(void); /* Consensus. */ void hs_dos_consensus_has_changed(const networkstatus_t *ns); +/* Introduction Point. */ bool hs_dos_can_send_intro2(or_circuit_t *s_intro_circ); - -/* Getters. */ -uint32_t hs_dos_get_intro2_rate(void); -uint32_t hs_dos_get_intro2_burst(void); +void hs_dos_setup_default_intro2_defenses(or_circuit_t *circ); #ifdef HS_DOS_PRIVATE #ifdef TOR_UNIT_TESTS +STATIC uint32_t get_intro2_enable_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_rate_consensus_param(const networkstatus_t *ns); +STATIC uint32_t get_intro2_burst_consensus_param(const networkstatus_t *ns); + #endif /* define(TOR_UNIT_TESTS) */ #endif /* defined(HS_DOS_PRIVATE) */ diff --git a/src/feature/hs/hs_intropoint.c b/src/feature/hs/hs_intropoint.c index 2c105f0b60..90a7f28943 100644 --- a/src/feature/hs/hs_intropoint.c +++ b/src/feature/hs/hs_intropoint.c @@ -26,6 +26,7 @@ #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_common.h" +#include "feature/hs/hs_config.h" #include "feature/hs/hs_descriptor.h" #include "feature/hs/hs_dos.h" #include "feature/hs/hs_intropoint.h" @@ -181,6 +182,185 @@ hs_intro_send_intro_established_cell,(or_circuit_t *circ)) return ret; } +/* Validate the cell DoS extension parameters. Return true iff they've been + * bound check and can be used. Else return false. See proposal 305 for + * details and reasons about this validation. */ +STATIC bool +cell_dos_extension_parameters_are_valid(uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec) +{ + bool ret = false; + + /* Check that received value is not below the minimum. Don't check if minimum + is set to 0, since the param is a positive value and gcc will complain. */ +#if HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN > 0 + if (intro2_rate_per_sec < HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too small. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } +#endif + + /* Check that received value is not above maximum */ + if (intro2_rate_per_sec > HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses rate per second is " + "too big. Received value: %" PRIu64, intro2_rate_per_sec); + goto end; + } + + /* Check that received value is not below the minimum */ +#if HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN > 0 + if (intro2_burst_per_sec < HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MIN) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too small. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } +#endif + + /* Check that received value is not above maximum */ + if (intro2_burst_per_sec > HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, + "Intro point DoS defenses burst per second is " + "too big. Received value: %" PRIu64, intro2_burst_per_sec); + goto end; + } + + /* In a rate limiting scenario, burst can never be smaller than the rate. At + * best it can be equal. */ + if (intro2_burst_per_sec < intro2_rate_per_sec) { + log_info(LD_REND, "Intro point DoS defenses burst is smaller than rate. " + "Rate: %" PRIu64 " vs Burst: %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + goto end; + } + + /* Passing validation. */ + ret = true; + + end: + return ret; +} + +/* Parse the cell DoS extension and apply defenses on the given circuit if + * validation passes. If the cell extension is malformed or contains unusable + * values, the DoS defenses is disabled on the circuit. */ +static void +handle_establish_intro_cell_dos_extension( + const trn_cell_extension_field_t *field, + or_circuit_t *circ) +{ + ssize_t ret; + uint64_t intro2_rate_per_sec = 0, intro2_burst_per_sec = 0; + trn_cell_extension_dos_t *dos = NULL; + + tor_assert(field); + tor_assert(circ); + + ret = trn_cell_extension_dos_parse(&dos, + trn_cell_extension_field_getconstarray_field(field), + trn_cell_extension_field_getlen_field(field)); + if (ret < 0) { + goto end; + } + + for (size_t i = 0; i < trn_cell_extension_dos_get_n_params(dos); i++) { + const trn_cell_extension_dos_param_t *param = + trn_cell_extension_dos_getconst_params(dos, i); + if (BUG(param == NULL)) { + goto end; + } + + switch (trn_cell_extension_dos_param_get_type(param)) { + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC: + intro2_rate_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + case TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC: + intro2_burst_per_sec = trn_cell_extension_dos_param_get_value(param); + break; + default: + goto end; + } + } + + /* A value of 0 is valid in the sense that we accept it but we still disable + * the defenses so return false. */ + if (intro2_rate_per_sec == 0 || intro2_burst_per_sec == 0) { + log_info(LD_REND, "Intro point DoS defenses parameter set to 0. " + "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + circ->introduce2_dos_defense_enabled = 0; + goto end; + } + + /* If invalid, we disable the defense on the circuit. */ + if (!cell_dos_extension_parameters_are_valid(intro2_rate_per_sec, + intro2_burst_per_sec)) { + circ->introduce2_dos_defense_enabled = 0; + log_info(LD_REND, "Disabling INTRO2 DoS defenses on circuit id %u", + circ->p_circ_id); + goto end; + } + + /* We passed validation, enable defenses and apply rate/burst. */ + circ->introduce2_dos_defense_enabled = 1; + + /* Initialize the INTRODUCE2 token bucket for the rate limiting. */ + token_bucket_ctr_init(&circ->introduce2_bucket, + (uint32_t) intro2_rate_per_sec, + (uint32_t) intro2_burst_per_sec, + (uint32_t) approx_time()); + log_info(LD_REND, "Intro point DoS defenses enabled. Rate is %" PRIu64 + " and Burst is %" PRIu64, + intro2_rate_per_sec, intro2_burst_per_sec); + + end: + trn_cell_extension_dos_free(dos); + return; +} + +/* Parse every cell extension in the given ESTABLISH_INTRO cell. */ +static void +handle_establish_intro_cell_extensions( + const trn_cell_establish_intro_t *parsed_cell, + or_circuit_t *circ) +{ + const trn_cell_extension_t *extensions; + + tor_assert(parsed_cell); + tor_assert(circ); + + extensions = trn_cell_establish_intro_getconst_extensions(parsed_cell); + if (extensions == NULL) { + goto end; + } + + /* Go over all extensions. */ + for (size_t idx = 0; idx < trn_cell_extension_get_num(extensions); idx++) { + const trn_cell_extension_field_t *field = + trn_cell_extension_getconst_fields(extensions, idx); + if (BUG(field == NULL)) { + /* The number of extensions should match the number of fields. */ + break; + } + + switch (trn_cell_extension_field_get_field_type(field)) { + case TRUNNEL_CELL_EXTENSION_TYPE_DOS: + /* After this, the circuit should be set for DoS defenses. */ + handle_establish_intro_cell_dos_extension(field, circ); + break; + default: + /* Unknown extension. Skip over. */ + break; + } + } + + end: + return; +} + /** We received an ESTABLISH_INTRO <b>parsed_cell</b> on <b>circ</b>. It's * well-formed and passed our verifications. Perform appropriate actions to * establish an intro point. */ @@ -193,6 +373,13 @@ handle_verified_establish_intro_cell(or_circuit_t *circ, get_auth_key_from_cell(&auth_key, RELAY_COMMAND_ESTABLISH_INTRO, parsed_cell); + /* Setup INTRODUCE2 defenses on the circuit. Must be done before parsing the + * cell extension that can possibly change the defenses' values. */ + hs_dos_setup_default_intro2_defenses(circ); + + /* Handle cell extension if any. */ + handle_establish_intro_cell_extensions(parsed_cell, circ); + /* Then notify the hidden service that the intro point is established by sending an INTRO_ESTABLISHED cell */ if (hs_intro_send_intro_established_cell(circ)) { @@ -204,9 +391,6 @@ handle_verified_establish_intro_cell(or_circuit_t *circ, hs_circuitmap_register_intro_circ_v3_relay_side(circ, &auth_key); /* Repurpose this circuit into an intro circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); - /* Initialize the INTRODUCE2 token bucket for the rate limiting. */ - token_bucket_ctr_init(&circ->introduce2_bucket, hs_dos_get_intro2_rate(), - hs_dos_get_intro2_burst(), (uint32_t) approx_time()); return 0; } diff --git a/src/feature/hs/hs_intropoint.h b/src/feature/hs/hs_intropoint.h index e82575f052..94ebf021e4 100644 --- a/src/feature/hs/hs_intropoint.h +++ b/src/feature/hs/hs_intropoint.h @@ -57,6 +57,9 @@ STATIC int handle_introduce1(or_circuit_t *client_circ, const uint8_t *request, size_t request_len); STATIC int validate_introduce1_parsed_cell(const trn_cell_introduce1_t *cell); STATIC int circuit_is_suitable_for_introduce1(const or_circuit_t *circ); +STATIC bool cell_dos_extension_parameters_are_valid( + uint64_t intro2_rate_per_sec, + uint64_t intro2_burst_per_sec); #endif /* defined(HS_INTROPOINT_PRIVATE) */ diff --git a/src/feature/hs/hs_service.c b/src/feature/hs/hs_service.c index 2835912742..7021190903 100644 --- a/src/feature/hs/hs_service.c +++ b/src/feature/hs/hs_service.c @@ -242,6 +242,9 @@ set_service_default_config(hs_service_config_t *c, c->is_single_onion = 0; c->dir_group_readable = 0; c->is_ephemeral = 0; + c->has_dos_defense_enabled = HS_CONFIG_V3_DOS_DEFENSE_DEFAULT; + c->intro_dos_rate_per_sec = HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT; + c->intro_dos_burst_per_sec = HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT; } /* From a service configuration object config, clear everything from it @@ -489,6 +492,10 @@ service_intro_point_new(const node_t *node) } } + /* Flag if this intro point supports the INTRO2 dos defenses. */ + ip->support_intro2_dos_defense = + node_supports_establish_intro_dos_extension(node); + /* Finally, copy onion key from the node. */ memcpy(&ip->onion_key, node_get_curve25519_onion_key(node), sizeof(ip->onion_key)); diff --git a/src/feature/hs/hs_service.h b/src/feature/hs/hs_service.h index 22aa00b2d7..c4bbb293bb 100644 --- a/src/feature/hs/hs_service.h +++ b/src/feature/hs/hs_service.h @@ -76,6 +76,10 @@ typedef struct hs_service_intro_point_t { * circuit associated with this intro point has received. This is used to * prevent replay attacks. */ replaycache_t *replay_cache; + + /* Support the INTRO2 DoS defense. If set, the DoS extension described by + * proposal 305 is sent. */ + unsigned int support_intro2_dos_defense : 1; } hs_service_intro_point_t; /* Object handling introduction points of a service. */ @@ -241,6 +245,11 @@ typedef struct hs_service_config_t { /* Does this service export the circuit ID of its clients? */ hs_circuit_id_protocol_t circuit_id_protocol; + + /* DoS defenses. For the ESTABLISH_INTRO cell extension. */ + unsigned int has_dos_defense_enabled : 1; + uint32_t intro_dos_rate_per_sec; + uint32_t intro_dos_burst_per_sec; } hs_service_config_t; /* Service state. */ diff --git a/src/feature/nodelist/nodelist.c b/src/feature/nodelist/nodelist.c index 6ae8d2bcb1..bd80fc1b4b 100644 --- a/src/feature/nodelist/nodelist.c +++ b/src/feature/nodelist/nodelist.c @@ -1106,7 +1106,7 @@ node_ed25519_id_matches(const node_t *node, const ed25519_public_key_t *id) /** Dummy object that should be unreturnable. Used to ensure that * node_get_protover_summary_flags() always returns non-NULL. */ static const protover_summary_flags_t zero_protover_flags = { - 0,0,0,0,0,0,0,0 + 0,0,0,0,0,0,0,0,0 }; /** Return the protover_summary_flags for a given node. */ @@ -1166,6 +1166,17 @@ node_supports_ed25519_hs_intro(const node_t *node) return node_get_protover_summary_flags(node)->supports_ed25519_hs_intro; } +/** Return true iff <b>node</b> supports the DoS ESTABLISH_INTRO cell + * extenstion. */ +int +node_supports_establish_intro_dos_extension(const node_t *node) +{ + tor_assert(node); + + return node_get_protover_summary_flags(node)-> + supports_establish_intro_dos_extension; +} + /** Return true iff <b>node</b> supports to be a rendezvous point for hidden * service version 3 (HSRend=2). */ int diff --git a/src/feature/nodelist/nodelist.h b/src/feature/nodelist/nodelist.h index 84ab5f7a54..af144c197f 100644 --- a/src/feature/nodelist/nodelist.h +++ b/src/feature/nodelist/nodelist.h @@ -76,6 +76,7 @@ int node_supports_ed25519_link_authentication(const node_t *node, int node_supports_v3_hsdir(const node_t *node); int node_supports_ed25519_hs_intro(const node_t *node); int node_supports_v3_rendezvous_point(const node_t *node); +int node_supports_establish_intro_dos_extension(const node_t *node); const uint8_t *node_get_rsa_id_digest(const node_t *node); smartlist_t *node_get_link_specifier_smartlist(const node_t *node, bool direct_conn); diff --git a/src/feature/rend/rendmid.c b/src/feature/rend/rendmid.c index bc86df632c..06471b2a7f 100644 --- a/src/feature/rend/rendmid.c +++ b/src/feature/rend/rendmid.c @@ -118,8 +118,7 @@ rend_mid_establish_intro_legacy(or_circuit_t *circ, const uint8_t *request, /* Now, set up this circuit. */ circuit_change_purpose(TO_CIRCUIT(circ), CIRCUIT_PURPOSE_INTRO_POINT); hs_circuitmap_register_intro_circ_v2_relay_side(circ, (uint8_t *)pk_digest); - token_bucket_ctr_init(&circ->introduce2_bucket, hs_dos_get_intro2_rate(), - hs_dos_get_intro2_burst(), (uint32_t) approx_time()); + hs_dos_setup_default_intro2_defenses(circ); log_info(LD_REND, "Established introduction point on circuit %u for service %s", |