summaryrefslogtreecommitdiff
path: root/src/feature
diff options
context:
space:
mode:
Diffstat (limited to 'src/feature')
-rw-r--r--src/feature/hs/hs_cell.c111
-rw-r--r--src/feature/hs/hs_cell.h11
-rw-r--r--src/feature/hs/hs_circuit.c2
-rw-r--r--src/feature/hs/hs_config.c60
-rw-r--r--src/feature/hs/hs_config.h9
-rw-r--r--src/feature/hs/hs_dos.c68
-rw-r--r--src/feature/hs/hs_dos.h10
-rw-r--r--src/feature/hs/hs_intropoint.c190
-rw-r--r--src/feature/hs/hs_intropoint.h3
-rw-r--r--src/feature/hs/hs_service.c7
-rw-r--r--src/feature/hs/hs_service.h9
-rw-r--r--src/feature/nodelist/nodelist.c13
-rw-r--r--src/feature/nodelist/nodelist.h1
-rw-r--r--src/feature/rend/rendmid.c3
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",