/** 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)
{
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) {
* 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);
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 >
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 */
end:
memwipe(onion_address, 0, sizeof(onion_address));
- tor_free(pow_solution);
return status;
}
#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. */
/* 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)) {
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;
+}