diff options
-rw-r--r-- | src/core/mainloop/cpuworker.c | 3 | ||||
-rw-r--r-- | src/core/or/circuituse.c | 4 | ||||
-rw-r--r-- | src/core/or/origin_circuit_st.h | 4 | ||||
-rw-r--r-- | src/feature/hs/hs_client.c | 31 | ||||
-rw-r--r-- | src/feature/hs/hs_client.h | 4 | ||||
-rw-r--r-- | src/feature/hs/hs_pow.c | 155 | ||||
-rw-r--r-- | src/feature/hs/hs_pow.h | 4 | ||||
-rw-r--r-- | src/lib/evloop/workqueue.c | 5 |
8 files changed, 191 insertions, 19 deletions
diff --git a/src/core/mainloop/cpuworker.c b/src/core/mainloop/cpuworker.c index 4a22790b44..a42dbb528d 100644 --- a/src/core/mainloop/cpuworker.c +++ b/src/core/mainloop/cpuworker.c @@ -14,7 +14,8 @@ * Right now, we use this infrastructure * <ul><li>for processing onionskins in onion.c * <li>for compressing consensuses in consdiffmgr.c, - * <li>and for calculating diffs and compressing them in consdiffmgr.c. + * <li>for calculating diffs and compressing them in consdiffmgr.c. + * <li>and for solving onion service PoW challenges in pow.c. * </ul> **/ #include "core/or/or.h" diff --git a/src/core/or/circuituse.c b/src/core/or/circuituse.c index d5879a21eb..b78f72e835 100644 --- a/src/core/or/circuituse.c +++ b/src/core/or/circuituse.c @@ -3043,8 +3043,8 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn) if (introcirc->base_.state == CIRCUIT_STATE_OPEN) { int ret; log_info(LD_REND, "Found open intro circ %u (id: %" PRIu32 "). " - "Rend circuit %u (id: %" PRIu32 "); Sending " - "introduction. (stream %d sec old)", + "Rend circuit %u (id: %" PRIu32 "); Considering " + "sending introduction. (stream %d sec old)", (unsigned) TO_CIRCUIT(introcirc)->n_circ_id, introcirc->global_identifier, (unsigned) TO_CIRCUIT(rendcirc)->n_circ_id, diff --git a/src/core/or/origin_circuit_st.h b/src/core/or/origin_circuit_st.h index fd5424c450..3b3fcc9b42 100644 --- a/src/core/or/origin_circuit_st.h +++ b/src/core/or/origin_circuit_st.h @@ -218,6 +218,10 @@ struct origin_circuit_t { * requests. */ unsigned int hs_with_pow_circ : 1; + /** Set iff this intro circ required a pow, and it has already queued + * the pow with the cpuworker and is awaiting a reply. */ + unsigned int hs_currently_solving_pow : 1; + /** Set iff this circuit has been given a relaxed timeout because * no circuits have opened. Used to prevent spamming logs. */ unsigned int relaxed_timeout : 1; diff --git a/src/feature/hs/hs_client.c b/src/feature/hs/hs_client.c index d7cfad7cd5..038f76c5b8 100644 --- a/src/feature/hs/hs_client.c +++ b/src/feature/hs/hs_client.c @@ -541,7 +541,7 @@ intro_circ_is_ok(const origin_circuit_t *circ) /** Find a descriptor intro point object that matches the given ident in the * given descriptor desc. Return NULL if not found. */ -static const hs_desc_intro_point_t * +const hs_desc_intro_point_t * find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, const hs_descriptor_t *desc) { @@ -670,7 +670,6 @@ consider_sending_introduce1(origin_circuit_t *intro_circ, char onion_address[HS_SERVICE_ADDR_LEN_BASE32 + 1]; const ed25519_public_key_t *service_identity_pk = NULL; const hs_desc_intro_point_t *ip; - hs_pow_solution_t *pow_solution = NULL; tor_assert(rend_circ); if (intro_circ_is_ok(intro_circ) < 0) { @@ -682,9 +681,15 @@ consider_sending_introduce1(origin_circuit_t *intro_circ, * version number but for now there is none because it's all v3. */ hs_build_address(service_identity_pk, HS_VERSION_THREE, onion_address); - log_info(LD_REND, "Sending INTRODUCE1 cell to service %s on circuit %u", + log_info(LD_REND, "Considering sending INTRODUCE1 cell to service %s " + "on circuit %u", safe_str_client(onion_address), TO_CIRCUIT(intro_circ)->n_circ_id); + /* if it's already waiting on the cpuworker farm, don't queue it again */ + if (intro_circ->hs_currently_solving_pow) { + goto tran_err; + } + /* 1) Get descriptor from our cache. */ const hs_descriptor_t *desc = hs_cache_lookup_as_client(service_identity_pk); @@ -731,7 +736,6 @@ consider_sending_introduce1(origin_circuit_t *intro_circ, if (desc->encrypted_data.pow_params && desc->encrypted_data.pow_params->suggested_effort > 0) { log_debug(LD_REND, "PoW params present in descriptor."); - pow_solution = tor_malloc_zero(sizeof(hs_pow_solution_t)); /* make sure we can't be tricked into hopeless quests */ if (desc->encrypted_data.pow_params->suggested_effort > @@ -746,15 +750,15 @@ consider_sending_introduce1(origin_circuit_t *intro_circ, CLIENT_MAX_POW_EFFORT; } - if (hs_pow_solve(desc->encrypted_data.pow_params, pow_solution)) { - log_warn(LD_REND, "Haven't solved the PoW yet."); - goto tran_err; - } - log_notice(LD_REND, "Got a PoW solution we like! Shipping it!"); - /* Set flag to reflect that the HS we are attempting to rendezvous has PoW - * defenses enabled, and as such we will need to be more lenient with - * timing out while waiting for the circuit to be built. */ - rend_circ->hs_with_pow_circ = 1; + /* send it to the client-side pow cpuworker for solving. */ + intro_circ->hs_currently_solving_pow = 1; + pow_queue_work(intro_circ->global_identifier, + rend_circ->global_identifier, + desc->encrypted_data.pow_params); + + /* can't proceed with the intro1 cell yet, so yield back to the + * main loop */ + goto tran_err; } /* move on to the next phase: actually try to send it */ @@ -781,7 +785,6 @@ consider_sending_introduce1(origin_circuit_t *intro_circ, end: memwipe(onion_address, 0, sizeof(onion_address)); - tor_free(pow_solution); return status; } diff --git a/src/feature/hs/hs_client.h b/src/feature/hs/hs_client.h index 37daeab943..e87cc00b75 100644 --- a/src/feature/hs/hs_client.h +++ b/src/feature/hs/hs_client.h @@ -78,6 +78,10 @@ typedef struct hs_client_service_authorization_t { int flags; } hs_client_service_authorization_t; +const hs_desc_intro_point_t * +find_desc_intro_point_by_ident(const hs_ident_circuit_t *ident, + const hs_descriptor_t *desc); + hs_client_register_auth_status_t hs_client_register_auth_credentials(hs_client_service_authorization_t *creds); diff --git a/src/feature/hs/hs_pow.c b/src/feature/hs/hs_pow.c index 49577617e6..2e94f9bced 100644 --- a/src/feature/hs/hs_pow.c +++ b/src/feature/hs/hs_pow.c @@ -13,9 +13,15 @@ typedef unsigned __int128 uint128_t; #include <stdio.h> #include "ext/ht.h" +#include "core/or/circuitlist.h" +#include "core/or/origin_circuit_st.h" +#include "feature/hs/hs_cache.h" #include "feature/hs/hs_descriptor.h" +#include "feature/hs/hs_client.h" #include "feature/hs/hs_pow.h" #include "lib/crypt_ops/crypto_rand.h" +#include "core/mainloop/cpuworker.h" +#include "lib/evloop/workqueue.h" /** Replay cache set up */ /** Cache entry for (nonce, seed) replay protection. */ @@ -144,7 +150,7 @@ hs_pow_solve(const hs_pow_desc_params_t *pow_params, /* We'll do a maximum of the nonce size iterations here which is the maximum * number of nonce we can try in an attempt to find a valid solution. */ - log_notice(LD_REND, "Solving proof of work"); + log_notice(LD_REND, "Solving proof of work (effort %u)", effort); for (uint64_t i = 0; i < UINT64_MAX; i++) { /* Calculate S = equix_solve(C || N || E) */ if (!equix_solve(ctx, challenge, HS_POW_CHALLENGE_LEN, solution)) { @@ -290,3 +296,150 @@ hs_pow_free_service_state(hs_pow_service_state_t *state) mainloop_event_free(state->pop_pqueue_ev); tor_free(state); } + +/* ===== + Thread workers + =====*/ + +/** + * An object passed to a worker thread that will try to solve the pow. + */ +typedef struct pow_worker_job_t { + + /** Input: The pow challenge we need to solve. */ + hs_pow_desc_params_t *pow_params; + + /** State: we'll look these up to figure out how to proceed after. */ + uint32_t intro_circ_identifier; + uint32_t rend_circ_identifier; + + /** Output: The worker thread will malloc and write its answer here, + * or set it to NULL if it produced no useful answer. */ + hs_pow_solution_t *pow_solution_out; + +} pow_worker_job_t; + +/** + * Worker function. This function runs inside a worker thread and receives + * a pow_worker_job_t as its input. + */ +static workqueue_reply_t +pow_worker_threadfn(void *state_, void *work_) +{ + (void)state_; + pow_worker_job_t *job = work_; + job->pow_solution_out = tor_malloc_zero(sizeof(hs_pow_solution_t)); + + if (hs_pow_solve(job->pow_params, job->pow_solution_out)) { + log_info(LD_REND, "Haven't solved the PoW yet. Returning."); + tor_free(job->pow_solution_out); + job->pow_solution_out = NULL; /* how we signal that we came up empty */ + return WQ_RPL_REPLY; + } + + /* we have a winner! */ + log_info(LD_REND, "cpuworker pow: we have a winner!"); + return WQ_RPL_REPLY; +} + +/** + * Helper: release all storage held in <b>job</b>. + */ +static void +pow_worker_job_free(pow_worker_job_t *job) +{ + if (!job) + return; + tor_free(job->pow_params); + tor_free(job->pow_solution_out); + tor_free(job); +} + +/** + * Worker function: This function runs in the main thread, and receives + * a pow_worker_job_t that the worker thread has already processed. + */ +static void +pow_worker_replyfn(void *work_) +{ + tor_assert(in_main_thread()); + tor_assert(work_); + + pow_worker_job_t *job = work_; + + // look up the circuits that we're going to use this pow in + origin_circuit_t *intro_circ = + circuit_get_by_global_id(job->intro_circ_identifier); + origin_circuit_t *rend_circ = + circuit_get_by_global_id(job->rend_circ_identifier); + + /* try to re-create desc and ip */ + const ed25519_public_key_t *service_identity_pk = NULL; + const hs_descriptor_t *desc = NULL; + const hs_desc_intro_point_t *ip = NULL; + if (intro_circ) + service_identity_pk = &intro_circ->hs_ident->identity_pk; + if (service_identity_pk) + desc = hs_cache_lookup_as_client(service_identity_pk); + if (desc) + ip = find_desc_intro_point_by_ident(intro_circ->hs_ident, desc); + + if (intro_circ && rend_circ && service_identity_pk && desc && ip && + job->pow_solution_out) { /* successful pow solve, and circs still here */ + + log_notice(LD_REND, "Got a PoW solution we like! Shipping it!"); + /* Set flag to reflect that the HS we are attempting to rendezvous has PoW + * defenses enabled, and as such we will need to be more lenient with + * timing out while waiting for the service-side circuit to be built. */ + rend_circ->hs_with_pow_circ = 1; + + // and then send that intro cell + if (send_introduce1(intro_circ, rend_circ, + desc, job->pow_solution_out, ip) < 0) { + /* if it failed, mark the intro point as ready to start over */ + intro_circ->hs_currently_solving_pow = 0; + } + + } else { /* unsuccessful pow solve. put it back on the queue. */ + log_notice(LD_REND, + "PoW cpuworker returned with no solution. Will retry soon."); + if (intro_circ) { + intro_circ->hs_currently_solving_pow = 0; + } + /* We could imagine immediately re-launching a follow-up worker + * here too, but for now just let the main intro loop find the + * not-being-serviced request and it can start everything again. For + * the sake of complexity, maybe that's the best long-term solution + * too, and we can tune the cpuworker job to try for longer if we want + * to improve efficiency. */ + } + + pow_worker_job_free(job); +} + +/** + * Queue the job of solving the pow in a worker thread. + */ +int +pow_queue_work(uint32_t intro_circ_identifier, + uint32_t rend_circ_identifier, + const hs_pow_desc_params_t *pow_params) +{ + tor_assert(in_main_thread()); + + pow_worker_job_t *job = tor_malloc_zero(sizeof(*job)); + job->intro_circ_identifier = intro_circ_identifier; + job->rend_circ_identifier = rend_circ_identifier; + job->pow_params = tor_memdup(pow_params, sizeof(hs_pow_desc_params_t)); + + workqueue_entry_t *work; + work = cpuworker_queue_work(WQ_PRI_LOW, + pow_worker_threadfn, + pow_worker_replyfn, + job); + if (!work) { + pow_worker_job_free(job); + return -1; + } + return 0; +} diff --git a/src/feature/hs/hs_pow.h b/src/feature/hs/hs_pow.h index bc0b823fd9..587cae6155 100644 --- a/src/feature/hs/hs_pow.h +++ b/src/feature/hs/hs_pow.h @@ -130,4 +130,8 @@ int hs_pow_verify(const hs_pow_service_state_t *pow_state, void hs_pow_remove_seed_from_cache(uint32_t seed); void hs_pow_free_service_state(hs_pow_service_state_t *state); +int pow_queue_work(uint32_t intro_circ_identifier, + uint32_t rend_circ_identifier, + const hs_pow_desc_params_t *pow_params); + #endif /* !defined(TOR_HS_POW_H) */ diff --git a/src/lib/evloop/workqueue.c b/src/lib/evloop/workqueue.c index bc929148eb..9a0c02fbd0 100644 --- a/src/lib/evloop/workqueue.c +++ b/src/lib/evloop/workqueue.c @@ -20,7 +20,10 @@ * The main thread can also queue an "update" that will be handled by all the * workers. This is useful for updating state that all the workers share. * - * In Tor today, there is currently only one thread pool, used in cpuworker.c. + * In Tor today, there is currently only one thread pool, managed + * in cpuworker.c and handling a variety of types of work, from the original + * "onion skin" circuit handshakes, to consensus diff computation, to + * client-side onion service PoW generation. */ #include "orconfig.h" |