diff --git a/changes/ticket30924 b/changes/ticket30924 new file mode 100644 index 0000000000..832c377972 --- /dev/null +++ b/changes/ticket30924 @@ -0,0 +1,6 @@ + o Major features (onion service v3, denial of service): + - Add onion service introduction denial of service defenses. They consist of + rate limiting client introduction at the intro point using parameters that + can be sent by the service within the ESTABLISH_INTRO cell. If the cell + extension for this is not used, the intro point will honor the consensus + parameters. Closes ticket 30924. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 362c409903..8359623625 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -2915,7 +2915,13 @@ on the public Tor network. HIDDEN SERVICE OPTIONS ---------------------- -The following options are used to configure a hidden service. +The following options are used to configure a hidden service. Some options +apply per service and some apply for the whole tor instance. + +The next section describes the per service options that can only be set +**after** the **HiddenServiceDir** directive + +**PER SERVICE OPTIONS:** [[HiddenServiceDir]] **HiddenServiceDir** __DIRECTORY__:: Store data files for a hidden service in DIRECTORY. Every hidden service @@ -2941,12 +2947,6 @@ The following options are used to configure a hidden service. connects to that VIRTPORT, one of the TARGETs from those lines will be chosen at random. Note that address-port pairs have to be comma-separated. -[[PublishHidServDescriptors]] **PublishHidServDescriptors** **0**|**1**:: - If set to 0, Tor will run any hidden services you configure, but it won't - advertise them to the rendezvous directory. This option is only useful if - you're using a Tor controller that handles hidserv publishing for you. - (Default: 1) - [[HiddenServiceVersion]] **HiddenServiceVersion** **2**|**3**:: A list of rendezvous service descriptor versions to publish for the hidden service. Currently, versions 2 and 3 are supported. (Default: 3) @@ -3025,6 +3025,38 @@ The following options are used to configure a hidden service. Number of introduction points the hidden service will have. You can't have more than 10 for v2 service and 20 for v3. (Default: 3) +[[HiddenServiceEnableIntroDoSDefense]] **HiddenServiceEnableIntroDoSDefense** **0**|**1**:: + Enable DoS defense at the intropoint level. When this is enabled, the + rate and burst parameter (see below) will be sent to the intro point which + will then use them to apply rate limiting for introduction request to this + service. + + + The introduction point honors the consensus parameters except if this is + specifically set by the service operator using this option. The service + never looks at the consensus parameters in order to enable or disable this + defense. (Default: 0) + +[[HiddenServiceEnableIntroDoSRatePerSec]] **HiddenServiceEnableIntroDoSRatePerSec** __NUM__:: + The allowed client introduction rate per second at the introduction + point. If this option is 0, it is considered infinite and thus if + **HiddenServiceEnableIntroDoSDefense** is set, it then effectively + disables the defenses. (Default: 25) + +[[HiddenServiceEnableIntroDoSBurstPerSec]] **HiddenServiceEnableIntroDoSBurstPerSec** __NUM__:: + The allowed client introduction burst per second at the introduction + point. If this option is 0, it is considered infinite and thus if + **HiddenServiceEnableIntroDoSDefense** is set, it then effectively + disables the defenses. (Default: 200) + + +**PER INSTANCE OPTIONS:** + +[[PublishHidServDescriptors]] **PublishHidServDescriptors** **0**|**1**:: + If set to 0, Tor will run any hidden services you configure, but it won't + advertise them to the rendezvous directory. This option is only useful if + you're using a Tor controller that handles hidserv publishing for you. + (Default: 1) + [[HiddenServiceSingleHopMode]] **HiddenServiceSingleHopMode** **0**|**1**:: **Experimental - Non Anonymous** Hidden Services on a tor instance in HiddenServiceSingleHopMode make one-hop (direct) circuits between the onion diff --git a/scripts/maint/practracker/exceptions.txt b/scripts/maint/practracker/exceptions.txt index 0acb6fb7f7..4201282bad 100644 --- a/scripts/maint/practracker/exceptions.txt +++ b/scripts/maint/practracker/exceptions.txt @@ -44,7 +44,6 @@ problem function-size /src/app/config/config.c:parse_dir_authority_line() 150 problem function-size /src/app/config/config.c:parse_dir_fallback_line() 101 problem function-size /src/app/config/config.c:parse_port_config() 446 problem function-size /src/app/config/config.c:parse_ports() 168 -problem function-size /src/app/config/config.c:getinfo_helper_config() 113 problem file-size /src/app/config/or_options_st.h 1112 problem include-count /src/app/main/main.c 68 problem function-size /src/app/main/main.c:dumpstats() 102 @@ -81,8 +80,8 @@ problem dependency-violation /src/core/mainloop/netstatus.c 4 problem dependency-violation /src/core/mainloop/periodic.c 2 problem dependency-violation /src/core/or/address_set.c 1 problem file-size /src/core/or/channel.c 3487 -problem file-size /src/core/or/channel.h 780 problem dependency-violation /src/core/or/channel.c 9 +problem file-size /src/core/or/channel.h 780 problem dependency-violation /src/core/or/channelpadding.c 6 problem function-size /src/core/or/channeltls.c:channel_tls_handle_var_cell() 160 problem function-size /src/core/or/channeltls.c:channel_tls_process_versions_cell() 170 @@ -105,10 +104,10 @@ problem dependency-violation /src/core/or/circuitlist.c 19 problem function-size /src/core/or/circuitmux.c:circuitmux_set_policy() 109 problem function-size /src/core/or/circuitmux.c:circuitmux_attach_circuit() 113 problem dependency-violation /src/core/or/circuitmux_ewma.c 2 -problem file-size /src/core/or/circuitpadding.c 3043 -problem function-size /src/core/or/circuitpadding.c:circpad_machine_schedule_padding() 107 -problem file-size /src/core/or/circuitpadding.h 809 +problem file-size /src/core/or/circuitpadding.c 3096 +problem function-size /src/core/or/circuitpadding.c:circpad_machine_schedule_padding() 113 problem dependency-violation /src/core/or/circuitpadding.c 6 +problem file-size /src/core/or/circuitpadding.h 813 problem function-size /src/core/or/circuitpadding_machines.c:circpad_machine_relay_hide_intro_circuits() 103 problem function-size /src/core/or/circuitpadding_machines.c:circpad_machine_client_hide_rend_circuits() 112 problem dependency-violation /src/core/or/circuitpadding_machines.c 1 @@ -142,19 +141,19 @@ problem include-count /src/core/or/connection_or.c 51 problem function-size /src/core/or/connection_or.c:connection_or_group_set_badness_() 105 problem function-size /src/core/or/connection_or.c:connection_or_client_learned_peer_id() 142 problem function-size /src/core/or/connection_or.c:connection_or_compute_authenticate_cell_body() 231 -problem file-size /src/core/or/or.h 1103 -problem include-count /src/core/or/or.h 49 problem dependency-violation /src/core/or/connection_or.c 20 problem dependency-violation /src/core/or/dos.c 5 problem dependency-violation /src/core/or/onion.c 2 +problem file-size /src/core/or/or.h 1107 +problem include-count /src/core/or/or.h 49 problem dependency-violation /src/core/or/or_periodic.c 1 problem file-size /src/core/or/policies.c 3249 problem function-size /src/core/or/policies.c:policy_summarize() 107 problem dependency-violation /src/core/or/policies.c 14 problem function-size /src/core/or/protover.c:protover_all_supported() 117 -problem function-size /src/core/or/relay.c:circuit_receive_relay_cell() 127 -problem file-size /src/core/or/relay.c 3263 problem dependency-violation /src/core/or/reasons.c 2 +problem file-size /src/core/or/relay.c 3264 +problem function-size /src/core/or/relay.c:circuit_receive_relay_cell() 127 problem function-size /src/core/or/relay.c:relay_send_command_from_edge_() 109 problem function-size /src/core/or/relay.c:connection_ap_process_end_not_open() 192 problem function-size /src/core/or/relay.c:connection_edge_process_relay_cell_not_open() 137 @@ -237,18 +236,19 @@ problem function-size /src/feature/dirparse/parsecommon.c:get_next_token() 158 problem function-size /src/feature/dirparse/routerparse.c:router_parse_entry_from_string() 554 problem function-size /src/feature/dirparse/routerparse.c:extrainfo_parse_entry_from_string() 208 problem function-size /src/feature/hibernate/hibernate.c:accounting_parse_options() 109 -problem function-size /src/feature/hs/hs_cell.c:hs_cell_build_establish_intro() 113 +problem function-size /src/feature/hs/hs_cell.c:hs_cell_build_establish_intro() 115 problem function-size /src/feature/hs/hs_cell.c:hs_cell_parse_introduce2() 152 problem function-size /src/feature/hs/hs_client.c:send_introduce1() 103 problem function-size /src/feature/hs/hs_client.c:hs_config_client_authorization() 107 problem function-size /src/feature/hs/hs_common.c:hs_get_responsible_hsdirs() 102 +problem function-size /src/feature/hs/hs_config.c:config_service_v3() 107 problem function-size /src/feature/hs/hs_config.c:config_generic_service() 138 problem function-size /src/feature/hs/hs_descriptor.c:desc_encode_v3() 101 problem function-size /src/feature/hs/hs_descriptor.c:decrypt_desc_layer() 105 problem function-size /src/feature/hs/hs_descriptor.c:decode_introduction_point() 122 problem function-size /src/feature/hs/hs_descriptor.c:desc_decode_superencrypted_v3() 107 problem function-size /src/feature/hs/hs_descriptor.c:desc_decode_encrypted_v3() 107 -problem file-size /src/feature/hs/hs_service.c 4109 +problem file-size /src/feature/hs/hs_service.c 4116 problem function-size /src/feature/keymgt/loadkey.c:ed_key_init_from_file() 326 problem function-size /src/feature/nodelist/authcert.c:trusted_dirs_load_certs_from_string() 123 problem function-size /src/feature/nodelist/authcert.c:authority_certs_fetch_missing() 295 @@ -261,7 +261,7 @@ problem function-size /src/feature/nodelist/node_select.c:router_pick_directory_ problem function-size /src/feature/nodelist/node_select.c:compute_weighted_bandwidths() 203 problem function-size /src/feature/nodelist/node_select.c:router_pick_trusteddirserver_impl() 112 problem function-size /src/feature/nodelist/nodelist.c:compute_frac_paths_available() 190 -problem file-size /src/feature/nodelist/routerlist.c 3239 +problem file-size /src/feature/nodelist/routerlist.c 3241 problem function-size /src/feature/nodelist/routerlist.c:router_rebuild_store() 148 problem function-size /src/feature/nodelist/routerlist.c:router_add_to_routerlist() 168 problem function-size /src/feature/nodelist/routerlist.c:routerlist_remove_old_routers() 121 @@ -280,7 +280,7 @@ problem function-size /src/feature/relay/routerkeys.c:load_ed_keys() 294 problem function-size /src/feature/rend/rendcache.c:rend_cache_store_v2_desc_as_client() 190 problem function-size /src/feature/rend/rendclient.c:rend_client_send_introduction() 219 problem function-size /src/feature/rend/rendcommon.c:rend_encode_v2_descriptors() 221 -problem function-size /src/feature/rend/rendmid.c:rend_mid_establish_intro_legacy() 103 +problem function-size /src/feature/rend/rendmid.c:rend_mid_establish_intro_legacy() 104 problem function-size /src/feature/rend/rendparse.c:rend_parse_v2_service_descriptor() 181 problem function-size /src/feature/rend/rendparse.c:rend_parse_introduction_points() 129 problem file-size /src/feature/rend/rendservice.c 4511 diff --git a/src/app/config/config.c b/src/app/config/config.c index 0cbc223d02..1babb0d4f8 100644 --- a/src/app/config/config.c +++ b/src/app/config/config.c @@ -496,6 +496,11 @@ static const config_var_t option_vars_[] = { VAR("HiddenServiceMaxStreamsCloseCircuit",LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceNumIntroductionPoints", LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceExportCircuitID", LINELIST_S, RendConfigLines, NULL), + VAR("HiddenServiceEnableIntroDoSDefense", LINELIST_S, RendConfigLines, NULL), + VAR("HiddenServiceEnableIntroDoSRatePerSec", + LINELIST_S, RendConfigLines, NULL), + VAR("HiddenServiceEnableIntroDoSBurstPerSec", + LINELIST_S, RendConfigLines, NULL), VAR("HiddenServiceStatistics", BOOL, HiddenServiceStatistics_option, "1"), V(HidServAuth, LINELIST, NULL), V(ClientOnionAuthDir, FILENAME, NULL), diff --git a/src/core/or/or.h b/src/core/or/or.h index ab258629a6..990cfacbc0 100644 --- a/src/core/or/or.h +++ b/src/core/or/or.h @@ -843,6 +843,10 @@ typedef struct protover_summary_flags_t { /** True iff this router has a protocol list that allows clients to * negotiate hs circuit setup padding. Requires Padding>=2. */ unsigned int supports_hs_setup_padding : 1; + + /** True iff this router has a protocol list that allows it to support the + * ESTABLISH_INTRO DoS cell extension. Requires HSIntro>=5. */ + unsigned int supports_establish_intro_dos_extension : 1; } protover_summary_flags_t; typedef struct routerinfo_t routerinfo_t; diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h index 8f319585a6..f3eb861613 100644 --- a/src/core/or/or_circuit_st.h +++ b/src/core/or/or_circuit_st.h @@ -72,6 +72,10 @@ struct or_circuit_t { * buffer stats to disk. */ uint64_t total_cell_waiting_time; + /** If set, the DoS defenses are enabled on this circuit meaning that the + * introduce2_bucket is initialized and used. */ + unsigned int introduce2_dos_defense_enabled : 1; + /** INTRODUCE2 cell bucket controlling how much can go on this circuit. Only * used if this is a service introduction circuit at the intro point * (purpose = CIRCUIT_PURPOSE_INTRO_POINT). */ diff --git a/src/core/or/protover.c b/src/core/or/protover.c index ccd33fabf7..905c5e9ed3 100644 --- a/src/core/or/protover.c +++ b/src/core/or/protover.c @@ -392,7 +392,7 @@ protover_get_supported_protocols(void) "Desc=1-2 " "DirCache=1-2 " "HSDir=1-2 " - "HSIntro=3-4 " + "HSIntro=3-5 " "HSRend=1-2 " "Link=1-5 " #ifdef HAVE_WORKING_TOR_TLS_GET_TLSSECRETS diff --git a/src/core/or/versions.c b/src/core/or/versions.c index 06417bb4eb..2c32b529f7 100644 --- a/src/core/or/versions.c +++ b/src/core/or/versions.c @@ -450,7 +450,9 @@ memoize_protover_summary(protover_summary_flags_t *out, PROTOVER_HS_RENDEZVOUS_POINT_V3); out->supports_hs_setup_padding = protocol_list_supports_protocol(protocols, PRT_PADDING, - PROTOVER_HS_SETUP_PADDING); + PROTOVER_HS_SETUP_PADDING); + out->supports_establish_intro_dos_extension = + protocol_list_supports_protocol(protocols, PRT_HSINTRO, 5); protover_summary_flags_t *new_cached = tor_memdup(out, sizeof(*out)); cached = strmap_set(protover_summary_map, protocols, new_cached); 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) +/* 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_rate_per_sec; -} + tor_assert(circ); -/* Return the INTRODUCE2 cell burst per second. */ -uint32_t -hs_dos_get_intro2_burst(void) -{ - return hs_dos_introduce_burst_per_sec; + 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 parsed_cell on circ. 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 node 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 node 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", diff --git a/src/test/test_hs_cell.c b/src/test/test_hs_cell.c index cdcbe23e69..403509fbc8 100644 --- a/src/test/test_hs_cell.c +++ b/src/test/test_hs_cell.c @@ -20,6 +20,7 @@ #include "feature/hs/hs_service.h" /* Trunnel. */ +#include "trunnel/hs/cell_common.h" #include "trunnel/hs/cell_establish_intro.h" /** We simulate the creation of an outgoing ESTABLISH_INTRO cell, and then we @@ -38,11 +39,13 @@ test_gen_establish_intro_cell(void *arg) /* Create outgoing ESTABLISH_INTRO cell and extract its payload so that we attempt to parse it. */ { + hs_service_config_t config; + memset(&config, 0, sizeof(config)); /* We only need the auth key pair here. */ hs_service_intro_point_t *ip = service_intro_point_new(NULL); /* Auth key pair is generated in the constructor so we are all set for * using this IP object. */ - ret = hs_cell_build_establish_intro(circ_nonce, ip, buf); + ret = hs_cell_build_establish_intro(circ_nonce, &config, ip, buf); service_intro_point_free(ip); tt_u64_op(ret, OP_GT, 0); } @@ -97,6 +100,9 @@ test_gen_establish_intro_cell_bad(void *arg) trn_cell_establish_intro_t *cell = NULL; char circ_nonce[DIGEST_LEN] = {0}; hs_service_intro_point_t *ip = NULL; + hs_service_config_t config; + + memset(&config, 0, sizeof(config)); MOCK(ed25519_sign_prefixed, mock_ed25519_sign_prefixed); @@ -108,7 +114,7 @@ test_gen_establish_intro_cell_bad(void *arg) cell = trn_cell_establish_intro_new(); tt_assert(cell); ip = service_intro_point_new(NULL); - cell_len = hs_cell_build_establish_intro(circ_nonce, ip, NULL); + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, NULL); service_intro_point_free(ip); expect_log_msg_containing("Unable to make signature for " "ESTABLISH_INTRO cell."); @@ -120,11 +126,97 @@ test_gen_establish_intro_cell_bad(void *arg) UNMOCK(ed25519_sign_prefixed); } +static void +test_gen_establish_intro_dos_ext(void *arg) +{ + ssize_t ret; + hs_service_config_t config; + hs_service_intro_point_t *ip = NULL; + trn_cell_extension_t *extensions = NULL; + trn_cell_extension_dos_t *dos = NULL; + + (void) arg; + + memset(&config, 0, sizeof(config)); + ip = service_intro_point_new(NULL); + tt_assert(ip); + ip->support_intro2_dos_defense = 1; + + /* Case 1: No DoS parameters so no extension to be built. */ + extensions = build_establish_intro_extensions(&config, ip); + tt_int_op(trn_cell_extension_get_num(extensions), OP_EQ, 0); + trn_cell_extension_free(extensions); + extensions = NULL; + + /* Case 2: Enable the DoS extension. Parameter set to 0 should indicate to + * disable the defense on the intro point but there should be an extension + * nonetheless in the cell. */ + config.has_dos_defense_enabled = 1; + extensions = build_establish_intro_extensions(&config, ip); + tt_int_op(trn_cell_extension_get_num(extensions), OP_EQ, 1); + /* Validate the extension. */ + const trn_cell_extension_field_t *field = + trn_cell_extension_getconst_fields(extensions, 0); + tt_int_op(trn_cell_extension_field_get_field_type(field), OP_EQ, + TRUNNEL_CELL_EXTENSION_TYPE_DOS); + ret = trn_cell_extension_dos_parse(&dos, + trn_cell_extension_field_getconstarray_field(field), + trn_cell_extension_field_getlen_field(field)); + tt_int_op(ret, OP_EQ, 19); + /* Rate per sec param. */ + const trn_cell_extension_dos_param_t *param = + trn_cell_extension_dos_getconst_params(dos, 0); + tt_int_op(trn_cell_extension_dos_param_get_type(param), OP_EQ, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC); + tt_u64_op(trn_cell_extension_dos_param_get_value(param), OP_EQ, 0); + /* Burst per sec param. */ + param = trn_cell_extension_dos_getconst_params(dos, 1); + tt_int_op(trn_cell_extension_dos_param_get_type(param), OP_EQ, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC); + tt_u64_op(trn_cell_extension_dos_param_get_value(param), OP_EQ, 0); + trn_cell_extension_dos_free(dos); dos = NULL; + trn_cell_extension_free(extensions); extensions = NULL; + + /* Case 3: Enable the DoS extension. Parameter set to some normal values. */ + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = 42; + config.intro_dos_burst_per_sec = 250; + extensions = build_establish_intro_extensions(&config, ip); + tt_int_op(trn_cell_extension_get_num(extensions), OP_EQ, 1); + /* Validate the extension. */ + field = trn_cell_extension_getconst_fields(extensions, 0); + tt_int_op(trn_cell_extension_field_get_field_type(field), OP_EQ, + TRUNNEL_CELL_EXTENSION_TYPE_DOS); + ret = trn_cell_extension_dos_parse(&dos, + trn_cell_extension_field_getconstarray_field(field), + trn_cell_extension_field_getlen_field(field)); + tt_int_op(ret, OP_EQ, 19); + /* Rate per sec param. */ + param = trn_cell_extension_dos_getconst_params(dos, 0); + tt_int_op(trn_cell_extension_dos_param_get_type(param), OP_EQ, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC); + tt_u64_op(trn_cell_extension_dos_param_get_value(param), OP_EQ, 42); + /* Burst per sec param. */ + param = trn_cell_extension_dos_getconst_params(dos, 1); + tt_int_op(trn_cell_extension_dos_param_get_type(param), OP_EQ, + TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC); + tt_u64_op(trn_cell_extension_dos_param_get_value(param), OP_EQ, 250); + trn_cell_extension_dos_free(dos); dos = NULL; + trn_cell_extension_free(extensions); extensions = NULL; + + done: + service_intro_point_free(ip); + trn_cell_extension_dos_free(dos); + trn_cell_extension_free(extensions); +} + struct testcase_t hs_cell_tests[] = { { "gen_establish_intro_cell", test_gen_establish_intro_cell, TT_FORK, NULL, NULL }, { "gen_establish_intro_cell_bad", test_gen_establish_intro_cell_bad, TT_FORK, NULL, NULL }, + { "gen_establish_intro_dos_ext", test_gen_establish_intro_dos_ext, TT_FORK, + NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_hs_config.c b/src/test/test_hs_config.c index c2c556307d..2b3afbb6e9 100644 --- a/src/test/test_hs_config.c +++ b/src/test/test_hs_config.c @@ -489,6 +489,111 @@ test_staging_service_v3(void *arg) hs_free_all(); } +static void +test_dos_parameters(void *arg) +{ + int ret; + + (void) arg; + + hs_init(); + + /* Valid configuration. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 22 1.1.1.1:22\n" + "HiddenServiceEnableIntroDoSDefense 1\n" + "HiddenServiceEnableIntroDoSRatePerSec 42\n" + "HiddenServiceEnableIntroDoSBurstPerSec 87\n"; + + setup_full_capture_of_logs(LOG_INFO); + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, 0); + expect_log_msg_containing("Service INTRO2 DoS defenses rate set to: 42"); + expect_log_msg_containing("Service INTRO2 DoS defenses burst set to: 87"); + teardown_capture_of_logs(); + } + + /* Invalid rate. Value of 2^37. Max allowed is 2^31. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 22 1.1.1.1:22\n" + "HiddenServiceEnableIntroDoSDefense 1\n" + "HiddenServiceEnableIntroDoSRatePerSec 137438953472\n" + "HiddenServiceEnableIntroDoSBurstPerSec 87\n"; + + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceEnableIntroDoSRatePerSec must " + "be between 0 and 2147483647, " + "not 137438953472"); + teardown_capture_of_logs(); + } + + /* Invalid burst. Value of 2^38. Max allowed is 2^31. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 22 1.1.1.1:22\n" + "HiddenServiceEnableIntroDoSDefense 1\n" + "HiddenServiceEnableIntroDoSRatePerSec 42\n" + "HiddenServiceEnableIntroDoSBurstPerSec 274877906944\n"; + + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceEnableIntroDoSBurstPerSec must " + "be between 0 and 2147483647, " + "not 274877906944"); + teardown_capture_of_logs(); + } + + /* Burst is smaller than rate. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 22 1.1.1.1:22\n" + "HiddenServiceEnableIntroDoSDefense 1\n" + "HiddenServiceEnableIntroDoSRatePerSec 42\n" + "HiddenServiceEnableIntroDoSBurstPerSec 27\n"; + + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("Hidden service DoS defenses burst (27) can " + "not be smaller than the rate value (42)."); + teardown_capture_of_logs(); + } + + /* Negative value. */ + { + const char *conf = + "HiddenServiceDir /tmp/tor-test-hs-RANDOM/hs3\n" + "HiddenServiceVersion 3\n" + "HiddenServicePort 22 1.1.1.1:22\n" + "HiddenServiceEnableIntroDoSDefense 1\n" + "HiddenServiceEnableIntroDoSRatePerSec -1\n" + "HiddenServiceEnableIntroDoSBurstPerSec 42\n"; + + setup_full_capture_of_logs(LOG_WARN); + ret = helper_config_service(conf, 0); + tt_int_op(ret, OP_EQ, -1); + expect_log_msg_containing("HiddenServiceEnableIntroDoSRatePerSec must be " + "between 0 and 2147483647, not -1"); + teardown_capture_of_logs(); + } + + done: + hs_free_all(); +} + struct testcase_t hs_config_tests[] = { /* Invalid service not specific to any version. */ { "invalid_service", test_invalid_service, TT_FORK, @@ -512,6 +617,10 @@ struct testcase_t hs_config_tests[] = { { "staging_service_v3", test_staging_service_v3, TT_FORK, NULL, NULL }, + /* Test HS DoS parameters. */ + { "dos_parameters", test_dos_parameters, TT_FORK, + NULL, NULL }, + END_OF_TESTCASES }; diff --git a/src/test/test_hs_dos.c b/src/test/test_hs_dos.c index 3dfa057a4a..f68639e24a 100644 --- a/src/test/test_hs_dos.c +++ b/src/test/test_hs_dos.c @@ -8,6 +8,8 @@ #define CIRCUITLIST_PRIVATE #define NETWORKSTATUS_PRIVATE +#define HS_DOS_PRIVATE +#define HS_INTROPOINT_PRIVATE #include "test/test.h" #include "test/test_helpers.h" @@ -20,6 +22,7 @@ #include "core/or/or_circuit_st.h" #include "feature/hs/hs_dos.h" +#include "feature/hs/hs_intropoint.h" #include "feature/nodelist/networkstatus.h" static void @@ -57,9 +60,8 @@ test_can_send_intro2(void *arg) /* Make that circuit a service intro point. */ circuit_change_purpose(TO_CIRCUIT(or_circ), CIRCUIT_PURPOSE_INTRO_POINT); - /* Initialize the INTRODUCE2 token bucket for the rate limiting. */ - token_bucket_ctr_init(&or_circ->introduce2_bucket, hs_dos_get_intro2_rate(), - hs_dos_get_intro2_burst(), now); + hs_dos_setup_default_intro2_defenses(or_circ); + or_circ->introduce2_dos_defense_enabled = 1; /* Brand new circuit, we should be able to send INTRODUCE2 cells. */ tt_int_op(true, OP_EQ, hs_dos_can_send_intro2(or_circ)); @@ -71,13 +73,13 @@ test_can_send_intro2(void *arg) tt_int_op(true, OP_EQ, hs_dos_can_send_intro2(or_circ)); } tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, - hs_dos_get_intro2_burst() - 10); + get_intro2_burst_consensus_param(NULL) - 10); /* Fully refill the bucket minus 1 cell. */ update_approx_time(++now); tt_int_op(true, OP_EQ, hs_dos_can_send_intro2(or_circ)); tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, - hs_dos_get_intro2_burst() - 1); + get_intro2_burst_consensus_param(NULL) - 1); /* Receive an INTRODUCE2 at each second. We should have the bucket full * since at every second it gets refilled. */ @@ -87,18 +89,18 @@ test_can_send_intro2(void *arg) } /* Last check if we can send the cell decrements the bucket so minus 1. */ tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, - hs_dos_get_intro2_burst() - 1); + get_intro2_burst_consensus_param(NULL) - 1); /* Manually reset bucket for next test. */ token_bucket_ctr_reset(&or_circ->introduce2_bucket, now); tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, - hs_dos_get_intro2_burst()); + get_intro2_burst_consensus_param(NULL)); /* Do a full burst in the current second which should empty the bucket and * we shouldn't be allowed to send one more cell after that. We go minus 1 * cell else the very last check if we can send the INTRO2 cell returns * false because the bucket goes down to 0. */ - for (uint32_t i = 0; i < hs_dos_get_intro2_burst() - 1; i++) { + for (uint32_t i = 0; i < get_intro2_burst_consensus_param(NULL) - 1; i++) { tt_int_op(true, OP_EQ, hs_dos_can_send_intro2(or_circ)); } tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, 1); @@ -116,7 +118,7 @@ test_can_send_intro2(void *arg) update_approx_time(++now); tt_int_op(true, OP_EQ, hs_dos_can_send_intro2(or_circ)); tt_uint_op(token_bucket_ctr_get(&or_circ->introduce2_bucket), OP_EQ, - hs_dos_get_intro2_rate() - 1); + get_intro2_rate_consensus_param(NULL) - 1); done: circuit_free_(TO_CIRCUIT(or_circ)); @@ -125,10 +127,50 @@ test_can_send_intro2(void *arg) free_mock_consensus(); } +static void +test_validate_dos_extension_params(void *arg) +{ + bool ret; + + (void) arg; + + /* Validate the default values. */ + ret = cell_dos_extension_parameters_are_valid( + get_intro2_rate_consensus_param(NULL), + get_intro2_burst_consensus_param(NULL)); + tt_assert(ret); + + /* Valid custom rate/burst. */ + ret = cell_dos_extension_parameters_are_valid(17, 42); + tt_assert(ret); + ret = cell_dos_extension_parameters_are_valid(INT32_MAX, INT32_MAX); + tt_assert(ret); + + /* Invalid rate. */ + ret = cell_dos_extension_parameters_are_valid(UINT64_MAX, 42); + tt_assert(!ret); + + /* Invalid burst. */ + ret = cell_dos_extension_parameters_are_valid(42, UINT64_MAX); + tt_assert(!ret); + + /* Value of 0 is valid (but should disable defenses) */ + ret = cell_dos_extension_parameters_are_valid(0, 0); + tt_assert(ret); + + /* Can't have burst smaller than rate. */ + ret = cell_dos_extension_parameters_are_valid(42, 40); + tt_assert(!ret); + + done: + return; +} + struct testcase_t hs_dos_tests[] = { { "can_send_intro2", test_can_send_intro2, TT_FORK, NULL, NULL }, + { "validate_dos_extension_params", test_validate_dos_extension_params, + TT_FORK, NULL, NULL }, END_OF_TESTCASES }; - diff --git a/src/test/test_hs_intropoint.c b/src/test/test_hs_intropoint.c index 7b01809f96..feb934d93c 100644 --- a/src/test/test_hs_intropoint.c +++ b/src/test/test_hs_intropoint.c @@ -26,6 +26,7 @@ #include "feature/hs/hs_cell.h" #include "feature/hs/hs_circuitmap.h" #include "feature/hs/hs_common.h" +#include "feature/hs/hs_config.h" #include "feature/hs/hs_dos.h" #include "feature/hs/hs_intropoint.h" #include "feature/hs/hs_service.h" @@ -45,6 +46,9 @@ new_establish_intro_cell(const char *circ_nonce, uint8_t buf[RELAY_PAYLOAD_SIZE] = {0}; trn_cell_establish_intro_t *cell = NULL; hs_service_intro_point_t *ip = NULL; + hs_service_config_t config; + + memset(&config, 0, sizeof(config)); /* Ensure that *cell_out is NULL such that we can use to check if we need to * free `cell` in case of an error. */ @@ -54,7 +58,7 @@ new_establish_intro_cell(const char *circ_nonce, * using this IP object. */ ip = service_intro_point_new(NULL); tt_assert(ip); - cell_len = hs_cell_build_establish_intro(circ_nonce, ip, buf); + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, buf); tt_i64_op(cell_len, OP_GT, 0); cell_len = trn_cell_establish_intro_parse(&cell, buf, sizeof(buf)); @@ -75,12 +79,15 @@ new_establish_intro_encoded_cell(const char *circ_nonce, uint8_t *cell_out) { ssize_t cell_len = 0; hs_service_intro_point_t *ip = NULL; + hs_service_config_t config; + + memset(&config, 0, sizeof(config)); /* Auth key pair is generated in the constructor so we are all set for * using this IP object. */ ip = service_intro_point_new(NULL); tt_assert(ip); - cell_len = hs_cell_build_establish_intro(circ_nonce, ip, cell_out); + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell_out); tt_i64_op(cell_len, OP_GT, 0); done: @@ -903,6 +910,153 @@ test_received_introduce1_handling(void *arg) UNMOCK(relay_send_command_from_edge_); } +static void +test_received_establish_intro_dos_ext(void *arg) +{ + int ret; + ssize_t cell_len = 0; + uint8_t cell[RELAY_PAYLOAD_SIZE] = {0}; + char circ_nonce[DIGEST_LEN] = {0}; + hs_service_intro_point_t *ip = NULL; + hs_service_config_t config; + or_circuit_t *intro_circ = or_circuit_new(0,NULL); + + (void) arg; + + MOCK(relay_send_command_from_edge_, mock_relay_send_command_from_edge); + + hs_circuitmap_init(); + + /* Setup. */ + crypto_rand(circ_nonce, sizeof(circ_nonce)); + ip = service_intro_point_new(NULL); + tt_assert(ip); + ip->support_intro2_dos_defense = 1; + memset(&config, 0, sizeof(config)); + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = 13; + config.intro_dos_burst_per_sec = 42; + helper_prepare_circ_for_intro(intro_circ, circ_nonce); + /* The INTRO2 bucket should be 0 at this point. */ + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, 0); + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, 0); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, 0); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, 0); + + /* Case 1: Build encoded cell. Usable DoS parameters. */ + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell); + tt_size_op(cell_len, OP_GT, 0); + /* Pass it to the intro point. */ + ret = hs_intro_received_establish_intro(intro_circ, cell, cell_len); + tt_int_op(ret, OP_EQ, 0); + /* Should be set to the burst value. */ + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, 42); + /* Validate the config of the intro2 bucket. */ + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, 13); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, 42); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, 1); + + /* Need to reset the circuit in between test cases. */ + circuit_free_(TO_CIRCUIT(intro_circ)); + intro_circ = or_circuit_new(0,NULL); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); + + /* Case 2: Build encoded cell. Bad DoS parameters. */ + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = UINT_MAX; + config.intro_dos_burst_per_sec = 13; + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell); + tt_size_op(cell_len, OP_GT, 0); + /* Pass it to the intro point. */ + ret = hs_intro_received_establish_intro(intro_circ, cell, cell_len); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT); + + /* Need to reset the circuit in between test cases. */ + circuit_free_(TO_CIRCUIT(intro_circ)); + intro_circ = or_circuit_new(0,NULL); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); + + /* Case 3: Build encoded cell. Burst is smaller than rate. Not allowed. */ + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = 87; + config.intro_dos_burst_per_sec = 45; + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell); + tt_size_op(cell_len, OP_GT, 0); + /* Pass it to the intro point. */ + ret = hs_intro_received_establish_intro(intro_circ, cell, cell_len); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT); + + /* Need to reset the circuit in between test cases. */ + circuit_free_(TO_CIRCUIT(intro_circ)); + intro_circ = or_circuit_new(0,NULL); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); + + /* Case 4: Build encoded cell. Rate is 0 but burst is not 0. Disables the + * defense. */ + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = 0; + config.intro_dos_burst_per_sec = 45; + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell); + tt_size_op(cell_len, OP_GT, 0); + /* Pass it to the intro point. */ + ret = hs_intro_received_establish_intro(intro_circ, cell, cell_len); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT); + + /* Need to reset the circuit in between test cases. */ + circuit_free_(TO_CIRCUIT(intro_circ)); + intro_circ = or_circuit_new(0,NULL); + helper_prepare_circ_for_intro(intro_circ, circ_nonce); + + /* Case 5: Build encoded cell. Burst is 0 but rate is not 0. Disables the + * defense. */ + config.has_dos_defense_enabled = 1; + config.intro_dos_rate_per_sec = 45; + config.intro_dos_burst_per_sec = 0; + cell_len = hs_cell_build_establish_intro(circ_nonce, &config, ip, cell); + tt_size_op(cell_len, OP_GT, 0); + /* Pass it to the intro point. */ + ret = hs_intro_received_establish_intro(intro_circ, cell, cell_len); + tt_int_op(ret, OP_EQ, 0); + tt_u64_op(token_bucket_ctr_get(&intro_circ->introduce2_bucket), OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_u64_op(intro_circ->introduce2_bucket.cfg.rate, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_RATE_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_bucket.cfg.burst, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_BURST_PER_SEC_DEFAULT); + tt_int_op(intro_circ->introduce2_dos_defense_enabled, OP_EQ, + HS_CONFIG_V3_DOS_DEFENSE_DEFAULT); + + done: + circuit_free_(TO_CIRCUIT(intro_circ)); + service_intro_point_free(ip); + hs_circuitmap_free_all(); + UNMOCK(relay_send_command_from_edge_); +} + static void * hs_subsystem_setup_fn(const struct testcase_t *tc) { @@ -961,5 +1115,8 @@ struct testcase_t hs_intropoint_tests[] = { { "received_introduce1_handling", test_received_introduce1_handling, TT_FORK, NULL, &test_setup}, + { "received_establish_intro_dos_ext", + test_received_establish_intro_dos_ext, TT_FORK, NULL, &test_setup}, + END_OF_TESTCASES }; diff --git a/src/trunnel/hs/cell_common.c b/src/trunnel/hs/cell_common.c index 830af5c78b..1f50961d69 100644 --- a/src/trunnel/hs/cell_common.c +++ b/src/trunnel/hs/cell_common.c @@ -28,10 +28,10 @@ int cellcommon_deadcode_dummy__ = 0; } \ } while (0) -trn_cell_extension_fields_t * -trn_cell_extension_fields_new(void) +trn_cell_extension_field_t * +trn_cell_extension_field_new(void) { - trn_cell_extension_fields_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_fields_t)); + trn_cell_extension_field_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_field_t)); if (NULL == val) return NULL; return val; @@ -40,7 +40,7 @@ trn_cell_extension_fields_new(void) /** Release all storage held inside 'obj', but do not free 'obj'. */ static void -trn_cell_extension_fields_clear(trn_cell_extension_fields_t *obj) +trn_cell_extension_field_clear(trn_cell_extension_field_t *obj) { (void) obj; TRUNNEL_DYNARRAY_WIPE(&obj->field); @@ -48,62 +48,62 @@ trn_cell_extension_fields_clear(trn_cell_extension_fields_t *obj) } void -trn_cell_extension_fields_free(trn_cell_extension_fields_t *obj) +trn_cell_extension_field_free(trn_cell_extension_field_t *obj) { if (obj == NULL) return; - trn_cell_extension_fields_clear(obj); - trunnel_memwipe(obj, sizeof(trn_cell_extension_fields_t)); + trn_cell_extension_field_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_extension_field_t)); trunnel_free_(obj); } uint8_t -trn_cell_extension_fields_get_field_type(const trn_cell_extension_fields_t *inp) +trn_cell_extension_field_get_field_type(const trn_cell_extension_field_t *inp) { return inp->field_type; } int -trn_cell_extension_fields_set_field_type(trn_cell_extension_fields_t *inp, uint8_t val) +trn_cell_extension_field_set_field_type(trn_cell_extension_field_t *inp, uint8_t val) { inp->field_type = val; return 0; } uint8_t -trn_cell_extension_fields_get_field_len(const trn_cell_extension_fields_t *inp) +trn_cell_extension_field_get_field_len(const trn_cell_extension_field_t *inp) { return inp->field_len; } int -trn_cell_extension_fields_set_field_len(trn_cell_extension_fields_t *inp, uint8_t val) +trn_cell_extension_field_set_field_len(trn_cell_extension_field_t *inp, uint8_t val) { inp->field_len = val; return 0; } size_t -trn_cell_extension_fields_getlen_field(const trn_cell_extension_fields_t *inp) +trn_cell_extension_field_getlen_field(const trn_cell_extension_field_t *inp) { return TRUNNEL_DYNARRAY_LEN(&inp->field); } uint8_t -trn_cell_extension_fields_get_field(trn_cell_extension_fields_t *inp, size_t idx) +trn_cell_extension_field_get_field(trn_cell_extension_field_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->field, idx); } uint8_t -trn_cell_extension_fields_getconst_field(const trn_cell_extension_fields_t *inp, size_t idx) +trn_cell_extension_field_getconst_field(const trn_cell_extension_field_t *inp, size_t idx) { - return trn_cell_extension_fields_get_field((trn_cell_extension_fields_t*)inp, idx); + return trn_cell_extension_field_get_field((trn_cell_extension_field_t*)inp, idx); } int -trn_cell_extension_fields_set_field(trn_cell_extension_fields_t *inp, size_t idx, uint8_t elt) +trn_cell_extension_field_set_field(trn_cell_extension_field_t *inp, size_t idx, uint8_t elt) { TRUNNEL_DYNARRAY_SET(&inp->field, idx, elt); return 0; } int -trn_cell_extension_fields_add_field(trn_cell_extension_fields_t *inp, uint8_t elt) +trn_cell_extension_field_add_field(trn_cell_extension_field_t *inp, uint8_t elt) { #if SIZE_MAX >= UINT8_MAX if (inp->field.n_ == UINT8_MAX) @@ -117,17 +117,17 @@ trn_cell_extension_fields_add_field(trn_cell_extension_fields_t *inp, uint8_t el } uint8_t * -trn_cell_extension_fields_getarray_field(trn_cell_extension_fields_t *inp) +trn_cell_extension_field_getarray_field(trn_cell_extension_field_t *inp) { return inp->field.elts_; } const uint8_t * -trn_cell_extension_fields_getconstarray_field(const trn_cell_extension_fields_t *inp) +trn_cell_extension_field_getconstarray_field(const trn_cell_extension_field_t *inp) { - return (const uint8_t *)trn_cell_extension_fields_getarray_field((trn_cell_extension_fields_t*)inp); + return (const uint8_t *)trn_cell_extension_field_getarray_field((trn_cell_extension_field_t*)inp); } int -trn_cell_extension_fields_setlen_field(trn_cell_extension_fields_t *inp, size_t newlen) +trn_cell_extension_field_setlen_field(trn_cell_extension_field_t *inp, size_t newlen) { uint8_t *newptr; #if UINT8_MAX < SIZE_MAX @@ -147,7 +147,7 @@ trn_cell_extension_fields_setlen_field(trn_cell_extension_fields_t *inp, size_t return -1; } const char * -trn_cell_extension_fields_check(const trn_cell_extension_fields_t *obj) +trn_cell_extension_field_check(const trn_cell_extension_field_t *obj) { if (obj == NULL) return "Object was NULL"; @@ -159,11 +159,11 @@ trn_cell_extension_fields_check(const trn_cell_extension_fields_t *obj) } ssize_t -trn_cell_extension_fields_encoded_len(const trn_cell_extension_fields_t *obj) +trn_cell_extension_field_encoded_len(const trn_cell_extension_field_t *obj) { ssize_t result = 0; - if (NULL != trn_cell_extension_fields_check(obj)) + if (NULL != trn_cell_extension_field_check(obj)) return -1; @@ -178,24 +178,24 @@ trn_cell_extension_fields_encoded_len(const trn_cell_extension_fields_t *obj) return result; } int -trn_cell_extension_fields_clear_errors(trn_cell_extension_fields_t *obj) +trn_cell_extension_field_clear_errors(trn_cell_extension_field_t *obj) { int r = obj->trunnel_error_code_; obj->trunnel_error_code_ = 0; return r; } ssize_t -trn_cell_extension_fields_encode(uint8_t *output, const size_t avail, const trn_cell_extension_fields_t *obj) +trn_cell_extension_field_encode(uint8_t *output, const size_t avail, const trn_cell_extension_field_t *obj) { ssize_t result = 0; size_t written = 0; uint8_t *ptr = output; const char *msg; #ifdef TRUNNEL_CHECK_ENCODED_LEN - const ssize_t encoded_len = trn_cell_extension_fields_encoded_len(obj); + const ssize_t encoded_len = trn_cell_extension_field_encoded_len(obj); #endif - if (NULL != (msg = trn_cell_extension_fields_check(obj))) + if (NULL != (msg = trn_cell_extension_field_check(obj))) goto check_failed; #ifdef TRUNNEL_CHECK_ENCODED_LEN @@ -252,11 +252,11 @@ trn_cell_extension_fields_encode(uint8_t *output, const size_t avail, const trn_ return result; } -/** As trn_cell_extension_fields_parse(), but do not allocate the +/** As trn_cell_extension_field_parse(), but do not allocate the * output object. */ static ssize_t -trn_cell_extension_fields_parse_into(trn_cell_extension_fields_t *obj, const uint8_t *input, const size_t len_in) +trn_cell_extension_field_parse_into(trn_cell_extension_field_t *obj, const uint8_t *input, const size_t len_in) { const uint8_t *ptr = input; size_t remaining = len_in; @@ -290,15 +290,15 @@ trn_cell_extension_fields_parse_into(trn_cell_extension_fields_t *obj, const uin } ssize_t -trn_cell_extension_fields_parse(trn_cell_extension_fields_t **output, const uint8_t *input, const size_t len_in) +trn_cell_extension_field_parse(trn_cell_extension_field_t **output, const uint8_t *input, const size_t len_in) { ssize_t result; - *output = trn_cell_extension_fields_new(); + *output = trn_cell_extension_field_new(); if (NULL == *output) return -1; - result = trn_cell_extension_fields_parse_into(*output, input, len_in); + result = trn_cell_extension_field_parse_into(*output, input, len_in); if (result < 0) { - trn_cell_extension_fields_free(*output); + trn_cell_extension_field_free(*output); *output = NULL; } return result; @@ -322,7 +322,7 @@ trn_cell_extension_clear(trn_cell_extension_t *obj) unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - trn_cell_extension_fields_free(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + trn_cell_extension_field_free(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); } } TRUNNEL_DYNARRAY_WIPE(&obj->fields); @@ -356,66 +356,66 @@ trn_cell_extension_getlen_fields(const trn_cell_extension_t *inp) return TRUNNEL_DYNARRAY_LEN(&inp->fields); } -struct trn_cell_extension_fields_st * +struct trn_cell_extension_field_st * trn_cell_extension_get_fields(trn_cell_extension_t *inp, size_t idx) { return TRUNNEL_DYNARRAY_GET(&inp->fields, idx); } - const struct trn_cell_extension_fields_st * + const struct trn_cell_extension_field_st * trn_cell_extension_getconst_fields(const trn_cell_extension_t *inp, size_t idx) { return trn_cell_extension_get_fields((trn_cell_extension_t*)inp, idx); } int -trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt) +trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_field_st * elt) { - trn_cell_extension_fields_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->fields, idx); + trn_cell_extension_field_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->fields, idx); if (oldval && oldval != elt) - trn_cell_extension_fields_free(oldval); + trn_cell_extension_field_free(oldval); return trn_cell_extension_set0_fields(inp, idx, elt); } int -trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt) +trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_field_st * elt) { TRUNNEL_DYNARRAY_SET(&inp->fields, idx, elt); return 0; } int -trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_fields_st * elt) +trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_field_st * elt) { #if SIZE_MAX >= UINT8_MAX if (inp->fields.n_ == UINT8_MAX) goto trunnel_alloc_failed; #endif - TRUNNEL_DYNARRAY_ADD(struct trn_cell_extension_fields_st *, &inp->fields, elt, {}); + TRUNNEL_DYNARRAY_ADD(struct trn_cell_extension_field_st *, &inp->fields, elt, {}); return 0; trunnel_alloc_failed: TRUNNEL_SET_ERROR_CODE(inp); return -1; } -struct trn_cell_extension_fields_st * * +struct trn_cell_extension_field_st * * trn_cell_extension_getarray_fields(trn_cell_extension_t *inp) { return inp->fields.elts_; } -const struct trn_cell_extension_fields_st * const * +const struct trn_cell_extension_field_st * const * trn_cell_extension_getconstarray_fields(const trn_cell_extension_t *inp) { - return (const struct trn_cell_extension_fields_st * const *)trn_cell_extension_getarray_fields((trn_cell_extension_t*)inp); + return (const struct trn_cell_extension_field_st * const *)trn_cell_extension_getarray_fields((trn_cell_extension_t*)inp); } int trn_cell_extension_setlen_fields(trn_cell_extension_t *inp, size_t newlen) { - struct trn_cell_extension_fields_st * *newptr; + struct trn_cell_extension_field_st * *newptr; #if UINT8_MAX < SIZE_MAX if (newlen > UINT8_MAX) goto trunnel_alloc_failed; #endif newptr = trunnel_dynarray_setlen(&inp->fields.allocated_, &inp->fields.n_, inp->fields.elts_, newlen, - sizeof(inp->fields.elts_[0]), (trunnel_free_fn_t) trn_cell_extension_fields_free, + sizeof(inp->fields.elts_[0]), (trunnel_free_fn_t) trn_cell_extension_field_free, &inp->trunnel_error_code_); if (newlen != 0 && newptr == NULL) goto trunnel_alloc_failed; @@ -437,7 +437,7 @@ trn_cell_extension_check(const trn_cell_extension_t *obj) unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - if (NULL != (msg = trn_cell_extension_fields_check(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)))) + if (NULL != (msg = trn_cell_extension_field_check(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)))) return msg; } } @@ -458,12 +458,12 @@ trn_cell_extension_encoded_len(const trn_cell_extension_t *obj) /* Length of u8 num */ result += 1; - /* Length of struct trn_cell_extension_fields fields[num] */ + /* Length of struct trn_cell_extension_field fields[num] */ { unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { - result += trn_cell_extension_fields_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + result += trn_cell_extension_field_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); } } return result; @@ -500,13 +500,13 @@ trn_cell_extension_encode(uint8_t *output, const size_t avail, const trn_cell_ex trunnel_set_uint8(ptr, (obj->num)); written += 1; ptr += 1; - /* Encode struct trn_cell_extension_fields fields[num] */ + /* Encode struct trn_cell_extension_field fields[num] */ { unsigned idx; for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->fields); ++idx) { trunnel_assert(written <= avail); - result = trn_cell_extension_fields_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); + result = trn_cell_extension_field_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->fields, idx)); if (result < 0) goto fail; /* XXXXXXX !*/ written += result; ptr += result; @@ -553,18 +553,18 @@ trn_cell_extension_parse_into(trn_cell_extension_t *obj, const uint8_t *input, c obj->num = (trunnel_get_uint8(ptr)); remaining -= 1; ptr += 1; - /* Parse struct trn_cell_extension_fields fields[num] */ - TRUNNEL_DYNARRAY_EXPAND(trn_cell_extension_fields_t *, &obj->fields, obj->num, {}); + /* Parse struct trn_cell_extension_field fields[num] */ + TRUNNEL_DYNARRAY_EXPAND(trn_cell_extension_field_t *, &obj->fields, obj->num, {}); { - trn_cell_extension_fields_t * elt; + trn_cell_extension_field_t * elt; unsigned idx; for (idx = 0; idx < obj->num; ++idx) { - result = trn_cell_extension_fields_parse(&elt, ptr, remaining); + result = trn_cell_extension_field_parse(&elt, ptr, remaining); if (result < 0) goto relay_fail; trunnel_assert((size_t)result <= remaining); remaining -= result; ptr += result; - TRUNNEL_DYNARRAY_ADD(trn_cell_extension_fields_t *, &obj->fields, elt, {trn_cell_extension_fields_free(elt);}); + TRUNNEL_DYNARRAY_ADD(trn_cell_extension_field_t *, &obj->fields, elt, {trn_cell_extension_field_free(elt);}); } } trunnel_assert(ptr + remaining == input + len_in); diff --git a/src/trunnel/hs/cell_common.h b/src/trunnel/hs/cell_common.h index c84d17d8e5..beb65e015f 100644 --- a/src/trunnel/hs/cell_common.h +++ b/src/trunnel/hs/cell_common.h @@ -8,112 +8,112 @@ #include #include "trunnel.h" -#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_FIELDS) -struct trn_cell_extension_fields_st { +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_FIELD) +struct trn_cell_extension_field_st { uint8_t field_type; uint8_t field_len; TRUNNEL_DYNARRAY_HEAD(, uint8_t) field; uint8_t trunnel_error_code_; }; #endif -typedef struct trn_cell_extension_fields_st trn_cell_extension_fields_t; +typedef struct trn_cell_extension_field_st trn_cell_extension_field_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION) struct trn_cell_extension_st { uint8_t num; - TRUNNEL_DYNARRAY_HEAD(, struct trn_cell_extension_fields_st *) fields; + TRUNNEL_DYNARRAY_HEAD(, struct trn_cell_extension_field_st *) fields; uint8_t trunnel_error_code_; }; #endif typedef struct trn_cell_extension_st trn_cell_extension_t; -/** Return a newly allocated trn_cell_extension_fields with all +/** Return a newly allocated trn_cell_extension_field with all * elements set to zero. */ -trn_cell_extension_fields_t *trn_cell_extension_fields_new(void); -/** Release all storage held by the trn_cell_extension_fields in +trn_cell_extension_field_t *trn_cell_extension_field_new(void); +/** Release all storage held by the trn_cell_extension_field in * 'victim'. (Do nothing if 'victim' is NULL.) */ -void trn_cell_extension_fields_free(trn_cell_extension_fields_t *victim); -/** Try to parse a trn_cell_extension_fields from the buffer in +void trn_cell_extension_field_free(trn_cell_extension_field_t *victim); +/** Try to parse a trn_cell_extension_field from the buffer in * 'input', using up to 'len_in' bytes from the input buffer. On * success, return the number of bytes consumed and set *output to the - * newly allocated trn_cell_extension_fields_t. On failure, return -2 + * newly allocated trn_cell_extension_field_t. On failure, return -2 * if the input appears truncated, and -1 if the input is otherwise * invalid. */ -ssize_t trn_cell_extension_fields_parse(trn_cell_extension_fields_t **output, const uint8_t *input, const size_t len_in); +ssize_t trn_cell_extension_field_parse(trn_cell_extension_field_t **output, const uint8_t *input, const size_t len_in); /** Return the number of bytes we expect to need to encode the - * trn_cell_extension_fields in 'obj'. On failure, return a negative + * trn_cell_extension_field in 'obj'. On failure, return a negative * value. Note that this value may be an overestimate, and can even be * an underestimate for certain unencodeable objects. */ -ssize_t trn_cell_extension_fields_encoded_len(const trn_cell_extension_fields_t *obj); -/** Try to encode the trn_cell_extension_fields from 'input' into the +ssize_t trn_cell_extension_field_encoded_len(const trn_cell_extension_field_t *obj); +/** Try to encode the trn_cell_extension_field from 'input' into the * buffer at 'output', using up to 'avail' bytes of the output buffer. * On success, return the number of bytes used. On failure, return -2 * if the buffer was not long enough, and -1 if the input was invalid. */ -ssize_t trn_cell_extension_fields_encode(uint8_t *output, size_t avail, const trn_cell_extension_fields_t *input); -/** Check whether the internal state of the trn_cell_extension_fields +ssize_t trn_cell_extension_field_encode(uint8_t *output, size_t avail, const trn_cell_extension_field_t *input); +/** Check whether the internal state of the trn_cell_extension_field * in 'obj' is consistent. Return NULL if it is, and a short message * if it is not. */ -const char *trn_cell_extension_fields_check(const trn_cell_extension_fields_t *obj); +const char *trn_cell_extension_field_check(const trn_cell_extension_field_t *obj); /** Clear any errors that were set on the object 'obj' by its setter * functions. Return true iff errors were cleared. */ -int trn_cell_extension_fields_clear_errors(trn_cell_extension_fields_t *obj); +int trn_cell_extension_field_clear_errors(trn_cell_extension_field_t *obj); /** Return the value of the field_type field of the - * trn_cell_extension_fields_t in 'inp' + * trn_cell_extension_field_t in 'inp' */ -uint8_t trn_cell_extension_fields_get_field_type(const trn_cell_extension_fields_t *inp); +uint8_t trn_cell_extension_field_get_field_type(const trn_cell_extension_field_t *inp); /** Set the value of the field_type field of the - * trn_cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_extension_field_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int trn_cell_extension_fields_set_field_type(trn_cell_extension_fields_t *inp, uint8_t val); +int trn_cell_extension_field_set_field_type(trn_cell_extension_field_t *inp, uint8_t val); /** Return the value of the field_len field of the - * trn_cell_extension_fields_t in 'inp' + * trn_cell_extension_field_t in 'inp' */ -uint8_t trn_cell_extension_fields_get_field_len(const trn_cell_extension_fields_t *inp); +uint8_t trn_cell_extension_field_get_field_len(const trn_cell_extension_field_t *inp); /** Set the value of the field_len field of the - * trn_cell_extension_fields_t in 'inp' to 'val'. Return 0 on success; + * trn_cell_extension_field_t in 'inp' to 'val'. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int trn_cell_extension_fields_set_field_len(trn_cell_extension_fields_t *inp, uint8_t val); +int trn_cell_extension_field_set_field_len(trn_cell_extension_field_t *inp, uint8_t val); /** Return the length of the dynamic array holding the field field of - * the trn_cell_extension_fields_t in 'inp'. + * the trn_cell_extension_field_t in 'inp'. */ -size_t trn_cell_extension_fields_getlen_field(const trn_cell_extension_fields_t *inp); +size_t trn_cell_extension_field_getlen_field(const trn_cell_extension_field_t *inp); /** Return the element at position 'idx' of the dynamic array field - * field of the trn_cell_extension_fields_t in 'inp'. + * field of the trn_cell_extension_field_t in 'inp'. */ -uint8_t trn_cell_extension_fields_get_field(trn_cell_extension_fields_t *inp, size_t idx); -/** As trn_cell_extension_fields_get_field, but take and return a - * const pointer +uint8_t trn_cell_extension_field_get_field(trn_cell_extension_field_t *inp, size_t idx); +/** As trn_cell_extension_field_get_field, but take and return a const + * pointer */ -uint8_t trn_cell_extension_fields_getconst_field(const trn_cell_extension_fields_t *inp, size_t idx); +uint8_t trn_cell_extension_field_getconst_field(const trn_cell_extension_field_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field - * field of the trn_cell_extension_fields_t in 'inp', so that it will + * field of the trn_cell_extension_field_t in 'inp', so that it will * hold the value 'elt'. */ -int trn_cell_extension_fields_set_field(trn_cell_extension_fields_t *inp, size_t idx, uint8_t elt); +int trn_cell_extension_field_set_field(trn_cell_extension_field_t *inp, size_t idx, uint8_t elt); /** Append a new element 'elt' to the dynamic array field field of the - * trn_cell_extension_fields_t in 'inp'. + * trn_cell_extension_field_t in 'inp'. */ -int trn_cell_extension_fields_add_field(trn_cell_extension_fields_t *inp, uint8_t elt); +int trn_cell_extension_field_add_field(trn_cell_extension_field_t *inp, uint8_t elt); /** Return a pointer to the variable-length array field field of * 'inp'. */ -uint8_t * trn_cell_extension_fields_getarray_field(trn_cell_extension_fields_t *inp); -/** As trn_cell_extension_fields_get_field, but take and return a - * const pointer +uint8_t * trn_cell_extension_field_getarray_field(trn_cell_extension_field_t *inp); +/** As trn_cell_extension_field_get_field, but take and return a const + * pointer */ -const uint8_t * trn_cell_extension_fields_getconstarray_field(const trn_cell_extension_fields_t *inp); +const uint8_t * trn_cell_extension_field_getconstarray_field(const trn_cell_extension_field_t *inp); /** Change the length of the variable-length array field field of * 'inp' to 'newlen'.Fill extra elements with 0. Return 0 on success; * return -1 and set the error code on 'inp' on failure. */ -int trn_cell_extension_fields_setlen_field(trn_cell_extension_fields_t *inp, size_t newlen); +int trn_cell_extension_field_setlen_field(trn_cell_extension_field_t *inp, size_t newlen); /** Return a newly allocated trn_cell_extension with all elements set * to zero. */ @@ -166,32 +166,32 @@ size_t trn_cell_extension_getlen_fields(const trn_cell_extension_t *inp); /** Return the element at position 'idx' of the dynamic array field * fields of the trn_cell_extension_t in 'inp'. */ -struct trn_cell_extension_fields_st * trn_cell_extension_get_fields(trn_cell_extension_t *inp, size_t idx); +struct trn_cell_extension_field_st * trn_cell_extension_get_fields(trn_cell_extension_t *inp, size_t idx); /** As trn_cell_extension_get_fields, but take and return a const * pointer */ - const struct trn_cell_extension_fields_st * trn_cell_extension_getconst_fields(const trn_cell_extension_t *inp, size_t idx); + const struct trn_cell_extension_field_st * trn_cell_extension_getconst_fields(const trn_cell_extension_t *inp, size_t idx); /** Change the element at position 'idx' of the dynamic array field * fields of the trn_cell_extension_t in 'inp', so that it will hold * the value 'elt'. Free the previous value, if any. */ -int trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt); +int trn_cell_extension_set_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_field_st * elt); /** As trn_cell_extension_set_fields, but does not free the previous * value. */ -int trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_fields_st * elt); +int trn_cell_extension_set0_fields(trn_cell_extension_t *inp, size_t idx, struct trn_cell_extension_field_st * elt); /** Append a new element 'elt' to the dynamic array field fields of * the trn_cell_extension_t in 'inp'. */ -int trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_fields_st * elt); +int trn_cell_extension_add_fields(trn_cell_extension_t *inp, struct trn_cell_extension_field_st * elt); /** Return a pointer to the variable-length array field fields of * 'inp'. */ -struct trn_cell_extension_fields_st * * trn_cell_extension_getarray_fields(trn_cell_extension_t *inp); +struct trn_cell_extension_field_st * * trn_cell_extension_getarray_fields(trn_cell_extension_t *inp); /** As trn_cell_extension_get_fields, but take and return a const * pointer */ -const struct trn_cell_extension_fields_st * const * trn_cell_extension_getconstarray_fields(const trn_cell_extension_t *inp); +const struct trn_cell_extension_field_st * const * trn_cell_extension_getconstarray_fields(const trn_cell_extension_t *inp); /** Change the length of the variable-length array field fields of * 'inp' to 'newlen'.Fill extra elements with NULL; free removed * elements. Return 0 on success; return -1 and set the error code on diff --git a/src/trunnel/hs/cell_common.trunnel b/src/trunnel/hs/cell_common.trunnel index 1aa6999de7..7e99cbfa66 100644 --- a/src/trunnel/hs/cell_common.trunnel +++ b/src/trunnel/hs/cell_common.trunnel @@ -1,6 +1,6 @@ /* This file contains common data structure that cells use. */ -struct trn_cell_extension_fields { +struct trn_cell_extension_field { u8 field_type; u8 field_len; u8 field[field_len]; @@ -8,5 +8,5 @@ struct trn_cell_extension_fields { struct trn_cell_extension { u8 num; - struct trn_cell_extension_fields fields[num]; + struct trn_cell_extension_field fields[num]; }; diff --git a/src/trunnel/hs/cell_establish_intro.c b/src/trunnel/hs/cell_establish_intro.c index 99ceadbda4..f31404c55f 100644 --- a/src/trunnel/hs/cell_establish_intro.c +++ b/src/trunnel/hs/cell_establish_intro.c @@ -36,6 +36,185 @@ ssize_t trn_cell_extension_encoded_len(const trn_cell_extension_t *obj); ssize_t trn_cell_extension_encode(uint8_t *output, size_t avail, const trn_cell_extension_t *input); const char *trn_cell_extension_check(const trn_cell_extension_t *obj); int trn_cell_extension_clear_errors(trn_cell_extension_t *obj); +trn_cell_extension_dos_param_t * +trn_cell_extension_dos_param_new(void) +{ + trn_cell_extension_dos_param_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_dos_param_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_extension_dos_param_clear(trn_cell_extension_dos_param_t *obj) +{ + (void) obj; +} + +void +trn_cell_extension_dos_param_free(trn_cell_extension_dos_param_t *obj) +{ + if (obj == NULL) + return; + trn_cell_extension_dos_param_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_extension_dos_param_t)); + trunnel_free_(obj); +} + +uint8_t +trn_cell_extension_dos_param_get_type(const trn_cell_extension_dos_param_t *inp) +{ + return inp->type; +} +int +trn_cell_extension_dos_param_set_type(trn_cell_extension_dos_param_t *inp, uint8_t val) +{ + inp->type = val; + return 0; +} +uint64_t +trn_cell_extension_dos_param_get_value(const trn_cell_extension_dos_param_t *inp) +{ + return inp->value; +} +int +trn_cell_extension_dos_param_set_value(trn_cell_extension_dos_param_t *inp, uint64_t val) +{ + inp->value = val; + return 0; +} +const char * +trn_cell_extension_dos_param_check(const trn_cell_extension_dos_param_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + return NULL; +} + +ssize_t +trn_cell_extension_dos_param_encoded_len(const trn_cell_extension_dos_param_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_extension_dos_param_check(obj)) + return -1; + + + /* Length of u8 type */ + result += 1; + + /* Length of u64 value */ + result += 8; + return result; +} +int +trn_cell_extension_dos_param_clear_errors(trn_cell_extension_dos_param_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_extension_dos_param_encode(uint8_t *output, const size_t avail, const trn_cell_extension_dos_param_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_extension_dos_param_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_extension_dos_param_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 type */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->type)); + written += 1; ptr += 1; + + /* Encode u64 value */ + trunnel_assert(written <= avail); + if (avail - written < 8) + goto truncated; + trunnel_set_uint64(ptr, trunnel_htonll(obj->value)); + written += 8; ptr += 8; + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_extension_dos_param_parse(), but do not allocate the + * output object. + */ +static ssize_t +trn_cell_extension_dos_param_parse_into(trn_cell_extension_dos_param_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 type */ + CHECK_REMAINING(1, truncated); + obj->type = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + + /* Parse u64 value */ + CHECK_REMAINING(8, truncated); + obj->value = trunnel_ntohll(trunnel_get_uint64(ptr)); + remaining -= 8; ptr += 8; + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; +} + +ssize_t +trn_cell_extension_dos_param_parse(trn_cell_extension_dos_param_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_extension_dos_param_new(); + if (NULL == *output) + return -1; + result = trn_cell_extension_dos_param_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_extension_dos_param_free(*output); + *output = NULL; + } + return result; +} trn_cell_establish_intro_t * trn_cell_establish_intro_new(void) { @@ -561,6 +740,296 @@ trn_cell_establish_intro_parse(trn_cell_establish_intro_t **output, const uint8_ } return result; } +trn_cell_extension_dos_t * +trn_cell_extension_dos_new(void) +{ + trn_cell_extension_dos_t *val = trunnel_calloc(1, sizeof(trn_cell_extension_dos_t)); + if (NULL == val) + return NULL; + return val; +} + +/** Release all storage held inside 'obj', but do not free 'obj'. + */ +static void +trn_cell_extension_dos_clear(trn_cell_extension_dos_t *obj) +{ + (void) obj; + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->params); ++idx) { + trn_cell_extension_dos_param_free(TRUNNEL_DYNARRAY_GET(&obj->params, idx)); + } + } + TRUNNEL_DYNARRAY_WIPE(&obj->params); + TRUNNEL_DYNARRAY_CLEAR(&obj->params); +} + +void +trn_cell_extension_dos_free(trn_cell_extension_dos_t *obj) +{ + if (obj == NULL) + return; + trn_cell_extension_dos_clear(obj); + trunnel_memwipe(obj, sizeof(trn_cell_extension_dos_t)); + trunnel_free_(obj); +} + +uint8_t +trn_cell_extension_dos_get_n_params(const trn_cell_extension_dos_t *inp) +{ + return inp->n_params; +} +int +trn_cell_extension_dos_set_n_params(trn_cell_extension_dos_t *inp, uint8_t val) +{ + inp->n_params = val; + return 0; +} +size_t +trn_cell_extension_dos_getlen_params(const trn_cell_extension_dos_t *inp) +{ + return TRUNNEL_DYNARRAY_LEN(&inp->params); +} + +struct trn_cell_extension_dos_param_st * +trn_cell_extension_dos_get_params(trn_cell_extension_dos_t *inp, size_t idx) +{ + return TRUNNEL_DYNARRAY_GET(&inp->params, idx); +} + + const struct trn_cell_extension_dos_param_st * +trn_cell_extension_dos_getconst_params(const trn_cell_extension_dos_t *inp, size_t idx) +{ + return trn_cell_extension_dos_get_params((trn_cell_extension_dos_t*)inp, idx); +} +int +trn_cell_extension_dos_set_params(trn_cell_extension_dos_t *inp, size_t idx, struct trn_cell_extension_dos_param_st * elt) +{ + trn_cell_extension_dos_param_t *oldval = TRUNNEL_DYNARRAY_GET(&inp->params, idx); + if (oldval && oldval != elt) + trn_cell_extension_dos_param_free(oldval); + return trn_cell_extension_dos_set0_params(inp, idx, elt); +} +int +trn_cell_extension_dos_set0_params(trn_cell_extension_dos_t *inp, size_t idx, struct trn_cell_extension_dos_param_st * elt) +{ + TRUNNEL_DYNARRAY_SET(&inp->params, idx, elt); + return 0; +} +int +trn_cell_extension_dos_add_params(trn_cell_extension_dos_t *inp, struct trn_cell_extension_dos_param_st * elt) +{ +#if SIZE_MAX >= UINT8_MAX + if (inp->params.n_ == UINT8_MAX) + goto trunnel_alloc_failed; +#endif + TRUNNEL_DYNARRAY_ADD(struct trn_cell_extension_dos_param_st *, &inp->params, elt, {}); + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} + +struct trn_cell_extension_dos_param_st * * +trn_cell_extension_dos_getarray_params(trn_cell_extension_dos_t *inp) +{ + return inp->params.elts_; +} +const struct trn_cell_extension_dos_param_st * const * +trn_cell_extension_dos_getconstarray_params(const trn_cell_extension_dos_t *inp) +{ + return (const struct trn_cell_extension_dos_param_st * const *)trn_cell_extension_dos_getarray_params((trn_cell_extension_dos_t*)inp); +} +int +trn_cell_extension_dos_setlen_params(trn_cell_extension_dos_t *inp, size_t newlen) +{ + struct trn_cell_extension_dos_param_st * *newptr; +#if UINT8_MAX < SIZE_MAX + if (newlen > UINT8_MAX) + goto trunnel_alloc_failed; +#endif + newptr = trunnel_dynarray_setlen(&inp->params.allocated_, + &inp->params.n_, inp->params.elts_, newlen, + sizeof(inp->params.elts_[0]), (trunnel_free_fn_t) trn_cell_extension_dos_param_free, + &inp->trunnel_error_code_); + if (newlen != 0 && newptr == NULL) + goto trunnel_alloc_failed; + inp->params.elts_ = newptr; + return 0; + trunnel_alloc_failed: + TRUNNEL_SET_ERROR_CODE(inp); + return -1; +} +const char * +trn_cell_extension_dos_check(const trn_cell_extension_dos_t *obj) +{ + if (obj == NULL) + return "Object was NULL"; + if (obj->trunnel_error_code_) + return "A set function failed on this object"; + { + const char *msg; + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->params); ++idx) { + if (NULL != (msg = trn_cell_extension_dos_param_check(TRUNNEL_DYNARRAY_GET(&obj->params, idx)))) + return msg; + } + } + if (TRUNNEL_DYNARRAY_LEN(&obj->params) != obj->n_params) + return "Length mismatch for params"; + return NULL; +} + +ssize_t +trn_cell_extension_dos_encoded_len(const trn_cell_extension_dos_t *obj) +{ + ssize_t result = 0; + + if (NULL != trn_cell_extension_dos_check(obj)) + return -1; + + + /* Length of u8 n_params */ + result += 1; + + /* Length of struct trn_cell_extension_dos_param params[n_params] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->params); ++idx) { + result += trn_cell_extension_dos_param_encoded_len(TRUNNEL_DYNARRAY_GET(&obj->params, idx)); + } + } + return result; +} +int +trn_cell_extension_dos_clear_errors(trn_cell_extension_dos_t *obj) +{ + int r = obj->trunnel_error_code_; + obj->trunnel_error_code_ = 0; + return r; +} +ssize_t +trn_cell_extension_dos_encode(uint8_t *output, const size_t avail, const trn_cell_extension_dos_t *obj) +{ + ssize_t result = 0; + size_t written = 0; + uint8_t *ptr = output; + const char *msg; +#ifdef TRUNNEL_CHECK_ENCODED_LEN + const ssize_t encoded_len = trn_cell_extension_dos_encoded_len(obj); +#endif + + if (NULL != (msg = trn_cell_extension_dos_check(obj))) + goto check_failed; + +#ifdef TRUNNEL_CHECK_ENCODED_LEN + trunnel_assert(encoded_len >= 0); +#endif + + /* Encode u8 n_params */ + trunnel_assert(written <= avail); + if (avail - written < 1) + goto truncated; + trunnel_set_uint8(ptr, (obj->n_params)); + written += 1; ptr += 1; + + /* Encode struct trn_cell_extension_dos_param params[n_params] */ + { + + unsigned idx; + for (idx = 0; idx < TRUNNEL_DYNARRAY_LEN(&obj->params); ++idx) { + trunnel_assert(written <= avail); + result = trn_cell_extension_dos_param_encode(ptr, avail - written, TRUNNEL_DYNARRAY_GET(&obj->params, idx)); + if (result < 0) + goto fail; /* XXXXXXX !*/ + written += result; ptr += result; + } + } + + + trunnel_assert(ptr == output + written); +#ifdef TRUNNEL_CHECK_ENCODED_LEN + { + trunnel_assert(encoded_len >= 0); + trunnel_assert((size_t)encoded_len == written); + } + +#endif + + return written; + + truncated: + result = -2; + goto fail; + check_failed: + (void)msg; + result = -1; + goto fail; + fail: + trunnel_assert(result < 0); + return result; +} + +/** As trn_cell_extension_dos_parse(), but do not allocate the output + * object. + */ +static ssize_t +trn_cell_extension_dos_parse_into(trn_cell_extension_dos_t *obj, const uint8_t *input, const size_t len_in) +{ + const uint8_t *ptr = input; + size_t remaining = len_in; + ssize_t result = 0; + (void)result; + + /* Parse u8 n_params */ + CHECK_REMAINING(1, truncated); + obj->n_params = (trunnel_get_uint8(ptr)); + remaining -= 1; ptr += 1; + + /* Parse struct trn_cell_extension_dos_param params[n_params] */ + TRUNNEL_DYNARRAY_EXPAND(trn_cell_extension_dos_param_t *, &obj->params, obj->n_params, {}); + { + trn_cell_extension_dos_param_t * elt; + unsigned idx; + for (idx = 0; idx < obj->n_params; ++idx) { + result = trn_cell_extension_dos_param_parse(&elt, ptr, remaining); + if (result < 0) + goto relay_fail; + trunnel_assert((size_t)result <= remaining); + remaining -= result; ptr += result; + TRUNNEL_DYNARRAY_ADD(trn_cell_extension_dos_param_t *, &obj->params, elt, {trn_cell_extension_dos_param_free(elt);}); + } + } + trunnel_assert(ptr + remaining == input + len_in); + return len_in - remaining; + + truncated: + return -2; + relay_fail: + trunnel_assert(result < 0); + return result; + trunnel_alloc_failed: + return -1; +} + +ssize_t +trn_cell_extension_dos_parse(trn_cell_extension_dos_t **output, const uint8_t *input, const size_t len_in) +{ + ssize_t result; + *output = trn_cell_extension_dos_new(); + if (NULL == *output) + return -1; + result = trn_cell_extension_dos_parse_into(*output, input, len_in); + if (result < 0) { + trn_cell_extension_dos_free(*output); + *output = NULL; + } + return result; +} trn_cell_intro_established_t * trn_cell_intro_established_new(void) { diff --git a/src/trunnel/hs/cell_establish_intro.h b/src/trunnel/hs/cell_establish_intro.h index 1908645aa6..1924d9cab6 100644 --- a/src/trunnel/hs/cell_establish_intro.h +++ b/src/trunnel/hs/cell_establish_intro.h @@ -10,6 +10,17 @@ struct trn_cell_extension_st; #define TRUNNEL_SHA3_256_LEN 32 +#define TRUNNEL_CELL_EXTENSION_TYPE_DOS 1 +#define TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC 1 +#define TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC 2 +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_DOS_PARAM) +struct trn_cell_extension_dos_param_st { + uint8_t type; + uint64_t value; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_extension_dos_param_st trn_cell_extension_dos_param_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_ESTABLISH_INTRO) struct trn_cell_establish_intro_st { const uint8_t *start_cell; @@ -26,6 +37,14 @@ struct trn_cell_establish_intro_st { }; #endif typedef struct trn_cell_establish_intro_st trn_cell_establish_intro_t; +#if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_DOS) +struct trn_cell_extension_dos_st { + uint8_t n_params; + TRUNNEL_DYNARRAY_HEAD(, struct trn_cell_extension_dos_param_st *) params; + uint8_t trunnel_error_code_; +}; +#endif +typedef struct trn_cell_extension_dos_st trn_cell_extension_dos_t; #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_INTRO_ESTABLISHED) struct trn_cell_intro_established_st { struct trn_cell_extension_st *extensions; @@ -33,6 +52,62 @@ struct trn_cell_intro_established_st { }; #endif typedef struct trn_cell_intro_established_st trn_cell_intro_established_t; +/** Return a newly allocated trn_cell_extension_dos_param with all + * elements set to zero. + */ +trn_cell_extension_dos_param_t *trn_cell_extension_dos_param_new(void); +/** Release all storage held by the trn_cell_extension_dos_param in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_extension_dos_param_free(trn_cell_extension_dos_param_t *victim); +/** Try to parse a trn_cell_extension_dos_param from the buffer in + * 'input', using up to 'len_in' bytes from the input buffer. On + * success, return the number of bytes consumed and set *output to the + * newly allocated trn_cell_extension_dos_param_t. On failure, return + * -2 if the input appears truncated, and -1 if the input is otherwise + * invalid. + */ +ssize_t trn_cell_extension_dos_param_parse(trn_cell_extension_dos_param_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_extension_dos_param in 'obj'. On failure, return a + * negative value. Note that this value may be an overestimate, and + * can even be an underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_extension_dos_param_encoded_len(const trn_cell_extension_dos_param_t *obj); +/** Try to encode the trn_cell_extension_dos_param from 'input' into + * the buffer at 'output', using up to 'avail' bytes of the output + * buffer. On success, return the number of bytes used. On failure, + * return -2 if the buffer was not long enough, and -1 if the input + * was invalid. + */ +ssize_t trn_cell_extension_dos_param_encode(uint8_t *output, size_t avail, const trn_cell_extension_dos_param_t *input); +/** Check whether the internal state of the + * trn_cell_extension_dos_param in 'obj' is consistent. Return NULL if + * it is, and a short message if it is not. + */ +const char *trn_cell_extension_dos_param_check(const trn_cell_extension_dos_param_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_extension_dos_param_clear_errors(trn_cell_extension_dos_param_t *obj); +/** Return the value of the type field of the + * trn_cell_extension_dos_param_t in 'inp' + */ +uint8_t trn_cell_extension_dos_param_get_type(const trn_cell_extension_dos_param_t *inp); +/** Set the value of the type field of the + * trn_cell_extension_dos_param_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_extension_dos_param_set_type(trn_cell_extension_dos_param_t *inp, uint8_t val); +/** Return the value of the value field of the + * trn_cell_extension_dos_param_t in 'inp' + */ +uint64_t trn_cell_extension_dos_param_get_value(const trn_cell_extension_dos_param_t *inp); +/** Set the value of the value field of the + * trn_cell_extension_dos_param_t in 'inp' to 'val'. Return 0 on + * success; return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_extension_dos_param_set_value(trn_cell_extension_dos_param_t *inp, uint64_t val); /** Return a newly allocated trn_cell_establish_intro with all * elements set to zero. */ @@ -216,6 +291,90 @@ const uint8_t * trn_cell_establish_intro_getconstarray_sig(const trn_cell_estab * -1 and set the error code on 'inp' on failure. */ int trn_cell_establish_intro_setlen_sig(trn_cell_establish_intro_t *inp, size_t newlen); +/** Return a newly allocated trn_cell_extension_dos with all elements + * set to zero. + */ +trn_cell_extension_dos_t *trn_cell_extension_dos_new(void); +/** Release all storage held by the trn_cell_extension_dos in + * 'victim'. (Do nothing if 'victim' is NULL.) + */ +void trn_cell_extension_dos_free(trn_cell_extension_dos_t *victim); +/** Try to parse a trn_cell_extension_dos from the buffer in 'input', + * using up to 'len_in' bytes from the input buffer. On success, + * return the number of bytes consumed and set *output to the newly + * allocated trn_cell_extension_dos_t. On failure, return -2 if the + * input appears truncated, and -1 if the input is otherwise invalid. + */ +ssize_t trn_cell_extension_dos_parse(trn_cell_extension_dos_t **output, const uint8_t *input, const size_t len_in); +/** Return the number of bytes we expect to need to encode the + * trn_cell_extension_dos in 'obj'. On failure, return a negative + * value. Note that this value may be an overestimate, and can even be + * an underestimate for certain unencodeable objects. + */ +ssize_t trn_cell_extension_dos_encoded_len(const trn_cell_extension_dos_t *obj); +/** Try to encode the trn_cell_extension_dos from 'input' into the + * buffer at 'output', using up to 'avail' bytes of the output buffer. + * On success, return the number of bytes used. On failure, return -2 + * if the buffer was not long enough, and -1 if the input was invalid. + */ +ssize_t trn_cell_extension_dos_encode(uint8_t *output, size_t avail, const trn_cell_extension_dos_t *input); +/** Check whether the internal state of the trn_cell_extension_dos in + * 'obj' is consistent. Return NULL if it is, and a short message if + * it is not. + */ +const char *trn_cell_extension_dos_check(const trn_cell_extension_dos_t *obj); +/** Clear any errors that were set on the object 'obj' by its setter + * functions. Return true iff errors were cleared. + */ +int trn_cell_extension_dos_clear_errors(trn_cell_extension_dos_t *obj); +/** Return the value of the n_params field of the + * trn_cell_extension_dos_t in 'inp' + */ +uint8_t trn_cell_extension_dos_get_n_params(const trn_cell_extension_dos_t *inp); +/** Set the value of the n_params field of the + * trn_cell_extension_dos_t in 'inp' to 'val'. Return 0 on success; + * return -1 and set the error code on 'inp' on failure. + */ +int trn_cell_extension_dos_set_n_params(trn_cell_extension_dos_t *inp, uint8_t val); +/** Return the length of the dynamic array holding the params field of + * the trn_cell_extension_dos_t in 'inp'. + */ +size_t trn_cell_extension_dos_getlen_params(const trn_cell_extension_dos_t *inp); +/** Return the element at position 'idx' of the dynamic array field + * params of the trn_cell_extension_dos_t in 'inp'. + */ +struct trn_cell_extension_dos_param_st * trn_cell_extension_dos_get_params(trn_cell_extension_dos_t *inp, size_t idx); +/** As trn_cell_extension_dos_get_params, but take and return a const + * pointer + */ + const struct trn_cell_extension_dos_param_st * trn_cell_extension_dos_getconst_params(const trn_cell_extension_dos_t *inp, size_t idx); +/** Change the element at position 'idx' of the dynamic array field + * params of the trn_cell_extension_dos_t in 'inp', so that it will + * hold the value 'elt'. Free the previous value, if any. + */ +int trn_cell_extension_dos_set_params(trn_cell_extension_dos_t *inp, size_t idx, struct trn_cell_extension_dos_param_st * elt); +/** As trn_cell_extension_dos_set_params, but does not free the + * previous value. + */ +int trn_cell_extension_dos_set0_params(trn_cell_extension_dos_t *inp, size_t idx, struct trn_cell_extension_dos_param_st * elt); +/** Append a new element 'elt' to the dynamic array field params of + * the trn_cell_extension_dos_t in 'inp'. + */ +int trn_cell_extension_dos_add_params(trn_cell_extension_dos_t *inp, struct trn_cell_extension_dos_param_st * elt); +/** Return a pointer to the variable-length array field params of + * 'inp'. + */ +struct trn_cell_extension_dos_param_st * * trn_cell_extension_dos_getarray_params(trn_cell_extension_dos_t *inp); +/** As trn_cell_extension_dos_get_params, but take and return a const + * pointer + */ +const struct trn_cell_extension_dos_param_st * const * trn_cell_extension_dos_getconstarray_params(const trn_cell_extension_dos_t *inp); +/** Change the length of the variable-length array field params of + * 'inp' to 'newlen'.Fill extra elements with NULL; free removed + * elements. Return 0 on success; return -1 and set the error code on + * 'inp' on failure. + */ +int trn_cell_extension_dos_setlen_params(trn_cell_extension_dos_t *inp, size_t newlen); /** Return a newly allocated trn_cell_intro_established with all * elements set to zero. */ diff --git a/src/trunnel/hs/cell_establish_intro.trunnel b/src/trunnel/hs/cell_establish_intro.trunnel index 011ee62a15..e30938f6c2 100644 --- a/src/trunnel/hs/cell_establish_intro.trunnel +++ b/src/trunnel/hs/cell_establish_intro.trunnel @@ -39,3 +39,26 @@ struct trn_cell_intro_established { /* Extension(s). Reserved fields. */ struct trn_cell_extension extensions; }; + +/* + * ESTABLISH_INTRO cell extensions. + */ + +const TRUNNEL_CELL_EXTENSION_TYPE_DOS = 0x01; + +/* DoS Parameter types. */ +const TRUNNEL_DOS_PARAM_TYPE_INTRO2_RATE_PER_SEC = 0x01; +const TRUNNEL_DOS_PARAM_TYPE_INTRO2_BURST_PER_SEC = 0x02; + +/* + * DoS Parameters Extension. See proposal 305 for more details. + */ +struct trn_cell_extension_dos_param { + u8 type; + u64 value; +}; + +struct trn_cell_extension_dos { + u8 n_params; + struct trn_cell_extension_dos_param params[n_params]; +};