From 4eb783e97b20c830a1a4dff91cef13bcfd4f713f Mon Sep 17 00:00:00 2001 From: David Goulet Date: Wed, 29 Jun 2022 11:56:05 -0400 Subject: [PATCH] hs: Priority queue for rendezvous requests If PoW are enabled, use a priority queue by effort for the rendezvous requests hooked into the mainloop. Signed-off-by: David Goulet --- src/feature/hs/hs_cell.c | 83 +++++++++++++-- src/feature/hs/hs_cell.h | 2 + src/feature/hs/hs_circuit.c | 142 ++++++++++++++++++++++--- src/feature/hs/hs_circuit.h | 21 +++- src/test/test_hs_service.c | 10 +- src/trunnel/hs/cell_introduce1.h | 4 +- src/trunnel/hs/cell_introduce1.trunnel | 4 +- 7 files changed, 236 insertions(+), 30 deletions(-) diff --git a/src/feature/hs/hs_cell.c b/src/feature/hs/hs_cell.c index 603d997c42..004a7fcbe1 100644 --- a/src/feature/hs/hs_cell.c +++ b/src/feature/hs/hs_cell.c @@ -391,7 +391,7 @@ build_introduce_pow_extension(const hs_pow_solution_t *pow_solution, /* We are creating a cell extension field of type PoW solution. */ field = trn_extension_field_new(); - trn_extension_field_set_field_type(field, TRUNNEL_CELL_EXTENSION_TYPE_POW); + trn_extension_field_set_field_type(field, TRUNNEL_EXT_TYPE_POW); /* Build PoW extension field. */ pow_ext = trn_cell_extension_pow_new(); @@ -399,7 +399,7 @@ build_introduce_pow_extension(const hs_pow_solution_t *pow_solution, /* Copy PoW solution values into PoW extension cell. */ /* Equi-X base scheme */ - trn_cell_extension_pow_set_pow_version(pow_ext, TRUNNEL_POW_EQUIX); + trn_cell_extension_pow_set_pow_version(pow_ext, TRUNNEL_POW_VERSION_EQUIX); memcpy(trn_cell_extension_pow_getarray_pow_nonce(pow_ext), &pow_solution->nonce, TRUNNEL_POW_NONCE_LEN); @@ -793,6 +793,62 @@ hs_cell_parse_intro_established(const uint8_t *payload, size_t payload_len) return ret; } +/** Parse the cell PoW solution extension. Return 0 on success and data + * structure is updated with the PoW effort. Return -1 on any kind of error + * including if PoW couldn't be verified. */ +static int +handle_introduce2_encrypted_cell_pow_extension(const hs_service_t *service, + const trn_extension_field_t *field, + hs_cell_introduce2_data_t *data) +{ + int ret = -1; + trn_cell_extension_pow_t *pow = NULL; + hs_pow_solution_t sol; + + tor_assert(field); + + if (trn_cell_extension_pow_parse(&pow, + trn_extension_field_getconstarray_field(field), + trn_extension_field_getlen_field(field)) < 0) { + goto end; + } + + /* There is only one version supported at the moment so validate we at least + * have that. */ + if (trn_cell_extension_pow_get_pow_version(pow) != + TRUNNEL_POW_VERSION_EQUIX) { + log_debug(LD_REND, "Unsupported PoW version. Malformed INTRODUCE2"); + goto end; + } + + /* Effort E */ + sol.effort = trn_cell_extension_pow_get_pow_effort(pow); + /* Seed C */ + sol.seed_head = trn_cell_extension_pow_get_pow_seed(pow); + /* Nonce N */ + memcpy(&sol.nonce, trn_cell_extension_pow_getconstarray_pow_nonce(pow), + HS_POW_NONCE_LEN); + /* Solution S */ + memcpy(&sol.equix_solution, + trn_cell_extension_pow_getconstarray_pow_solution(pow), + HS_POW_EQX_SOL_LEN); + + if (hs_pow_verify(service->state.pow_state, &sol)) { + log_info(LD_REND, "PoW INTRODUCE2 request failed to verify."); + goto end; + } + + log_info(LD_REND, "PoW INTRODUCE2 request successfully verified."); + data->rdv_data.pow_effort = sol.effort; + + /* Successfully parsed and verified the PoW solution */ + ret = 0; + + end: + trn_cell_extension_pow_free(pow); + return ret; +} + /** For the encrypted INTRO2 cell in encrypted_section, use the crypto * material in data to compute the right ntor keys. Also validate the * INTRO2 MAC to ensure that the keys are the right ones. @@ -862,11 +918,15 @@ get_introduce2_keys_and_verify_mac(hs_cell_introduce2_data_t *data, } /** Parse the given INTRODUCE cell extension. Update the data object - * accordingly depending on the extension. */ -static void -parse_introduce_cell_extension(hs_cell_introduce2_data_t *data, + * accordingly depending on the extension. Return 0 if it validated + * correctly, or return -1 if it is malformed (for example because it + * includes a PoW that doesn't verify). */ +static int +parse_introduce_cell_extension(const hs_service_t *service, + hs_cell_introduce2_data_t *data, const trn_extension_field_t *field) { + int ret = 0; trn_extension_field_cc_t *cc_field = NULL; tor_assert(data); @@ -879,11 +939,20 @@ parse_introduce_cell_extension(hs_cell_introduce2_data_t *data, data->pv.protocols_known = 1; data->pv.supports_congestion_control = data->rdv_data.cc_enabled; break; + case TRUNNEL_EXT_TYPE_POW: + /* PoW request. If successful, the effort is put in the data. */ + if (handle_introduce2_encrypted_cell_pow_extension(service, + field, data) < 0) { + log_fn(LOG_PROTOCOL_WARN, LD_REND, "Invalid PoW cell extension."); + ret = -1; + } + break; default: break; } trn_extension_field_cc_free(cc_field); + return ret; } /** Parse the INTRODUCE2 cell using data which contains everything we need to @@ -1026,7 +1095,9 @@ hs_cell_parse_introduce2(hs_cell_introduce2_data_t *data, /* The number of extensions should match the number of fields. */ break; } - parse_introduce_cell_extension(data, field); + if (parse_introduce_cell_extension(service, data, field) < 0) { + goto done; + } } } diff --git a/src/feature/hs/hs_cell.h b/src/feature/hs/hs_cell.h index 61c0a94b20..7b547991e6 100644 --- a/src/feature/hs/hs_cell.h +++ b/src/feature/hs/hs_cell.h @@ -60,6 +60,8 @@ typedef struct hs_cell_intro_rdv_data_t { smartlist_t *link_specifiers; /** Congestion control parameters. */ unsigned int cc_enabled : 1; + /** PoW effort. */ + uint32_t pow_effort; } hs_cell_intro_rdv_data_t; /** This data structure contains data that we need to parse an INTRODUCE2 cell diff --git a/src/feature/hs/hs_circuit.c b/src/feature/hs/hs_circuit.c index 835cd366ad..cbb3e0bfdd 100644 --- a/src/feature/hs/hs_circuit.c +++ b/src/feature/hs/hs_circuit.c @@ -310,8 +310,9 @@ get_service_anonymity_string(const hs_service_t *service) * MAX_REND_FAILURES then it will give up. */ MOCK_IMPL(STATIC void, launch_rendezvous_point_circuit,(const hs_service_t *service, - const hs_service_intro_point_t *ip, - const hs_cell_introduce2_data_t *data)) + const ed25519_public_key_t *ip_auth_pubkey, + const curve25519_keypair_t *ip_enc_key_kp, + const hs_cell_intro_rdv_data_t *rdv_data)) { int circ_needs_uptime; time_t now = time(NULL); @@ -319,15 +320,16 @@ launch_rendezvous_point_circuit,(const hs_service_t *service, origin_circuit_t *circ; tor_assert(service); - tor_assert(ip); - tor_assert(data); + tor_assert(ip_auth_pubkey); + tor_assert(ip_enc_key_kp); + tor_assert(rdv_data); circ_needs_uptime = hs_service_requires_uptime_circ(service->config.ports); /* Get the extend info data structure for the chosen rendezvous point * specified by the given link specifiers. */ - info = hs_get_extend_info_from_lspecs(data->rdv_data.link_specifiers, - &data->rdv_data.onion_pk, + info = hs_get_extend_info_from_lspecs(rdv_data->link_specifiers, + &rdv_data->onion_pk, service->config.is_single_onion); if (info == NULL) { /* We are done here, we can't extend to the rendezvous point. */ @@ -375,7 +377,7 @@ launch_rendezvous_point_circuit,(const hs_service_t *service, "for %s service %s", safe_str_client(extend_info_describe(info)), safe_str_client(hex_str((const char *) - data->rdv_data.rendezvous_cookie, + rdv_data->rendezvous_cookie, REND_COOKIE_LEN)), get_service_anonymity_string(service), safe_str_client(service->onion_address)); @@ -392,10 +394,10 @@ launch_rendezvous_point_circuit,(const hs_service_t *service, * key will be used for the RENDEZVOUS1 cell that will be sent on the * circuit once opened. */ curve25519_keypair_generate(&ephemeral_kp, 0); - if (hs_ntor_service_get_rendezvous1_keys(&ip->auth_key_kp.pubkey, - &ip->enc_key_kp, + if (hs_ntor_service_get_rendezvous1_keys(ip_auth_pubkey, + ip_enc_key_kp, &ephemeral_kp, - &data->rdv_data.client_pk, + &rdv_data->client_pk, &keys) < 0) { /* This should not really happened but just in case, don't make tor * freak out, close the circuit and move on. */ @@ -406,7 +408,7 @@ launch_rendezvous_point_circuit,(const hs_service_t *service, goto end; } circ->hs_ident = create_rp_circuit_identifier(service, - data->rdv_data.rendezvous_cookie, + rdv_data->rendezvous_cookie, &ephemeral_kp.pubkey, &keys); memwipe(&ephemeral_kp, 0, sizeof(ephemeral_kp)); memwipe(&keys, 0, sizeof(keys)); @@ -414,7 +416,7 @@ launch_rendezvous_point_circuit,(const hs_service_t *service, } /* Setup congestion control if asked by the client from the INTRO cell. */ - if (data->rdv_data.cc_enabled) { + if (rdv_data->cc_enabled) { hs_circ_setup_congestion_control(circ, congestion_control_sendme_inc(), service->config.is_single_onion); } @@ -600,6 +602,102 @@ cleanup_on_free_client_circ(circuit_t *circ) * Thus possible that this passes through. */ } +/** Return less than 0 if a precedes b, 0 if a equals b and greater than 0 if + * b precedes a. */ +static int +compare_rend_request_by_effort_(const void *_a, const void *_b) +{ + const pending_rend_t *a = _a, *b = _b; + if (a->rdv_data.pow_effort < b->rdv_data.pow_effort) { + return -1; + } else if (a->rdv_data.pow_effort == b->rdv_data.pow_effort) { + return 0; + } else { + return 1; + } +} + +static void +handle_rend_pqueue_cb(mainloop_event_t *ev, void *arg) +{ + (void) ev; /* Not using the returned event, make compiler happy. */ + hs_service_t *service = arg; + hs_pow_service_state_t *pow_state = service->state.pow_state; + + /* Pop next request by effort. */ + pending_rend_t *req = + smartlist_pqueue_pop(pow_state->rend_request_pqueue, + compare_rend_request_by_effort_, + offsetof(pending_rend_t, idx)); + + log_info(LD_REND, "Dequeued pending rendezvous request with effort: %u. " + "Remaining requests: %u", + req->rdv_data.pow_effort, + smartlist_len(pow_state->rend_request_pqueue)); + + /* Launch the rendezvous circuit. */ + launch_rendezvous_point_circuit(service, &req->ip_auth_pubkey, + &req->ip_enc_key_kp, &req->rdv_data); + + /* Clean memory. */ + link_specifier_smartlist_free(req->rdv_data.link_specifiers); + memwipe(req, 0, sizeof(pending_rend_t)); + tor_free(req); + + /* If there are still some pending rendezvous circuits in the pqueue then + * reschedule the event in order to continue handling them. */ + if (smartlist_len(pow_state->rend_request_pqueue) > 0) { + mainloop_event_activate(pow_state->pop_pqueue_ev); + } +} + +/** HRPR: Given the information needed to launch a rendezvous circuit and an + * effort value, enqueue the rendezvous request in the service's PoW priority + * queue with the effort being the priority. */ +static void +enqueue_rend_request(const hs_service_t *service, hs_service_intro_point_t *ip, + hs_cell_introduce2_data_t *data) +{ + hs_pow_service_state_t *pow_state = NULL; + pending_rend_t *req = NULL; + + tor_assert(service); + tor_assert(ip); + tor_assert(data); + + /* Ease our lives */ + pow_state = service->state.pow_state; + req = tor_malloc_zero(sizeof(pending_rend_t)); + + /* Copy over the rendezvous request the needed data to launch a circuit. */ + ed25519_pubkey_copy(&req->ip_auth_pubkey, &ip->auth_key_kp.pubkey); + memcpy(&req->ip_enc_key_kp, &ip->enc_key_kp, sizeof(req->ip_enc_key_kp)); + memcpy(&req->rdv_data, &data->rdv_data, sizeof(req->rdv_data)); + /* Invalidate the link specifier pointer in the introduce2 data so it + * doesn't get freed under us. */ + data->rdv_data.link_specifiers = NULL; + req->idx = -1; + + /* Enqueue the rendezvous request. */ + smartlist_pqueue_add(pow_state->rend_request_pqueue, + compare_rend_request_by_effort_, + offsetof(pending_rend_t, idx), req); + + log_info(LD_REND, "Enqueued rendezvous request with effort: %u. " + "Remaining requests: %u", + req->rdv_data.pow_effort, + smartlist_len(pow_state->rend_request_pqueue)); + + /* Initialize the priority queue event if it hasn't been done so already. */ + if (pow_state->pop_pqueue_ev == NULL) { + pow_state->pop_pqueue_ev = + mainloop_event_new(handle_rend_pqueue_cb, (void *)service); + } + + /* Activate event, we just enqueued a rendezvous request. */ + mainloop_event_activate(pow_state->pop_pqueue_ev); +} + /* ========== */ /* Public API */ /* ========== */ @@ -1008,6 +1106,7 @@ hs_circ_handle_introduce2(const hs_service_t *service, data.replay_cache = ip->replay_cache; data.rdv_data.link_specifiers = smartlist_new(); data.rdv_data.cc_enabled = 0; + data.rdv_data.pow_effort = 0; if (get_subcredential_for_handling_intro2_cell(service, &data, subcredential)) { @@ -1045,12 +1144,29 @@ hs_circ_handle_introduce2(const hs_service_t *service, * so increment our counter that we've seen one on this intro point. */ ip->introduce2_count++; + /* Add the rendezvous request to the priority queue if PoW defenses are + * enabled, otherwise rendezvous as usual. */ + if (service->config.has_pow_defenses_enabled) { + log_debug(LD_REND, "Adding introduction request to pqueue with effort: %u", + data.rdv_data.pow_effort); + enqueue_rend_request(service, ip, &data); + + /* Increase the total effort in valid requests received this period. */ + service->state.pow_state->total_effort += data.rdv_data.pow_effort; + + /* Successfully added rend circuit to priority queue. */ + ret = 0; + goto done; + } + /* Launch rendezvous circuit with the onion key and rend cookie. */ - launch_rendezvous_point_circuit(service, ip, &data); + launch_rendezvous_point_circuit(service, &ip->auth_key_kp.pubkey, + &ip->enc_key_kp, &data.rdv_data); /* Success. */ ret = 0; done: + /* Note that if PoW defenses are enabled, this is NULL. */ link_specifier_smartlist_free(data.rdv_data.link_specifiers); memwipe(&data, 0, sizeof(data)); return ret; diff --git a/src/feature/hs/hs_circuit.h b/src/feature/hs/hs_circuit.h index 3c84abaad2..8f888c569e 100644 --- a/src/feature/hs/hs_circuit.h +++ b/src/feature/hs/hs_circuit.h @@ -12,8 +12,23 @@ #include "core/or/or.h" #include "lib/crypt_ops/crypto_ed25519.h" +#include "feature/hs/hs_cell.h" #include "feature/hs/hs_service.h" +/* HRPR TODO Putting this here for now... */ +typedef struct pending_rend_t { + /* Intro point authentication pubkey. */ + ed25519_public_key_t ip_auth_pubkey; + /* Intro point encryption keypair for the "ntor" type. */ + curve25519_keypair_t ip_enc_key_kp; + + /* Rendezvous data for the circuit. */ + hs_cell_intro_rdv_data_t rdv_data; + + /** Position of element in the heap */ + int idx; +} pending_rend_t; + /* Cleanup function when the circuit is closed or freed. */ void hs_circ_cleanup_on_close(circuit_t *circ); void hs_circ_cleanup_on_free(circuit_t *circ); @@ -84,11 +99,11 @@ create_rp_circuit_identifier(const hs_service_t *service, const curve25519_public_key_t *server_pk, const struct hs_ntor_rend_cell_keys_t *keys); -struct hs_cell_introduce2_data_t; MOCK_DECL(STATIC void, launch_rendezvous_point_circuit,(const hs_service_t *service, - const hs_service_intro_point_t *ip, - const struct hs_cell_introduce2_data_t *data)); + const ed25519_public_key_t *ip_auth_pubkey, + const curve25519_keypair_t *ip_enc_key_kp, + const hs_cell_intro_rdv_data_t *rdv_data)); #endif /* defined(HS_CIRCUIT_PRIVATE) */ diff --git a/src/test/test_hs_service.c b/src/test/test_hs_service.c index 4a8a758b3f..eb905714c1 100644 --- a/src/test/test_hs_service.c +++ b/src/test/test_hs_service.c @@ -2279,12 +2279,14 @@ mock_build_state_get_exit_node(cpath_build_state_t *state) static void mock_launch_rendezvous_point_circuit(const hs_service_t *service, - const hs_service_intro_point_t *ip, - const hs_cell_introduce2_data_t *data) + const ed25519_public_key_t *ip_auth_pubkey, + const curve25519_keypair_t *ip_enc_key_kp, + const hs_cell_intro_rdv_data_t *rdv_data) { (void) service; - (void) ip; - (void) data; + (void) ip_auth_pubkey; + (void) ip_enc_key_kp; + (void) rdv_data; return; } diff --git a/src/trunnel/hs/cell_introduce1.h b/src/trunnel/hs/cell_introduce1.h index 89339e1a0d..90d34f37f2 100644 --- a/src/trunnel/hs/cell_introduce1.h +++ b/src/trunnel/hs/cell_introduce1.h @@ -19,10 +19,10 @@ struct link_specifier_st; #define TRUNNEL_HS_INTRO_AUTH_KEY_TYPE_LEGACY1 1 #define TRUNNEL_HS_INTRO_AUTH_KEY_TYPE_ED25519 2 #define TRUNNEL_HS_INTRO_ONION_KEY_TYPE_NTOR 1 -#define TRUNNEL_CELL_EXTENSION_TYPE_POW 1 +#define TRUNNEL_EXT_TYPE_POW 2 #define TRUNNEL_POW_NONCE_LEN 16 #define TRUNNEL_POW_SOLUTION_LEN 16 -#define TRUNNEL_POW_EQUIX 1 +#define TRUNNEL_POW_VERSION_EQUIX 1 #if !defined(TRUNNEL_OPAQUE) && !defined(TRUNNEL_OPAQUE_TRN_CELL_EXTENSION_POW) struct trn_cell_extension_pow_st { uint8_t pow_version; diff --git a/src/trunnel/hs/cell_introduce1.trunnel b/src/trunnel/hs/cell_introduce1.trunnel index 18865ddc02..35e00bed94 100644 --- a/src/trunnel/hs/cell_introduce1.trunnel +++ b/src/trunnel/hs/cell_introduce1.trunnel @@ -79,7 +79,7 @@ struct trn_cell_introduce_encrypted { */ /* Cell extension type PoW. */ -const TRUNNEL_CELL_EXTENSION_TYPE_POW = 0x01; +const TRUNNEL_EXT_TYPE_POW = 0x02; /* * HRPR: PoW Solution Extension. Proposal 327. @@ -88,7 +88,7 @@ const TRUNNEL_CELL_EXTENSION_TYPE_POW = 0x01; const TRUNNEL_POW_NONCE_LEN = 16; const TRUNNEL_POW_SOLUTION_LEN = 16; /* Version 1 is based on Equi-X scheme. */ -const TRUNNEL_POW_EQUIX = 0x01; +const TRUNNEL_POW_VERSION_EQUIX = 0x01; struct trn_cell_extension_pow { /* Type of PoW system used. */