]> git.ipfire.org Git - thirdparty/tor.git/commitdiff
hs_pow: modified approach to pqueue level thresholds
authorMicah Elizabeth Scott <beth@torproject.org>
Mon, 10 Apr 2023 22:27:33 +0000 (15:27 -0700)
committerMicah Elizabeth Scott <beth@torproject.org>
Wed, 10 May 2023 14:41:37 +0000 (07:41 -0700)
This centralizes the logic for deciding on these magic thresholds,
and tries to reduce them to just two: a min and max. The min should be a
"nearly empty" threshold, indicating that the queue only contains work
we expect to be able to complete very soon. The max level triggers a
bulk culling process that reduces the queue to half that amount.

This patch calculates both thresholds based on the torrc pqueue rate
settings if they're present, and uses generic defaults if the user asked
for an unlimited dequeue rate in torrc.

Signed-off-by: Micah Elizabeth Scott <beth@torproject.org>
src/feature/hs/hs_circuit.c
src/feature/hs/hs_pow.h
src/feature/hs/hs_service.c

index 1287b2bedace29c3054150e9db3e083ba0d7dea4..c3f2fbfc1e53fb0527c9b77e6c2a4ca2ecb6a4a5 100644 (file)
@@ -650,14 +650,8 @@ queued_rend_request_is_too_old(pending_rend_t *req, time_t now)
   return 0;
 }
 
-/** Maximum number of rendezvous requests we enqueue per service. We allow the
- * average amount of INTRODUCE2 that a service can process in a second times
- * the rendezvous timeout. Then we let it grow to twice that before
- * discarding the bottom half in trim_rend_pqueue(). */
-#define QUEUED_REND_REQUEST_HIGH_WATER (2 * 180 * MAX_REND_TIMEOUT)
-
 /** Our rendezvous request priority queue is too full; keep the first
- * QUEUED_REND_REQUEST_HIGH_WATER/2 entries and discard the rest.
+ * pqueue_high_level/2 entries and discard the rest.
  */
 static void
 trim_rend_pqueue(hs_pow_service_state_t *pow_state, time_t now)
@@ -670,7 +664,7 @@ trim_rend_pqueue(hs_pow_service_state_t *pow_state, time_t now)
                     smartlist_len(old_pqueue));
 
   while (smartlist_len(old_pqueue) &&
-         smartlist_len(new_pqueue) < QUEUED_REND_REQUEST_HIGH_WATER/2) {
+         smartlist_len(new_pqueue) < pow_state->pqueue_high_level/2) {
     /* while there are still old ones, and the new one isn't full yet */
     pending_rend_t *req =
       smartlist_pqueue_pop(old_pqueue,
@@ -752,12 +746,6 @@ rend_pqueue_clear(hs_pow_service_state_t *pow_state)
   }
 }
 
-/** How many rendezvous request we handle per mainloop event. Per prop327,
- * handling an INTRODUCE2 cell takes on average 5.56msec on an average CPU and
- * so it means that launching this max amount of circuits is well below 0.08
- * seconds which we believe is negligable on the whole mainloop. */
-#define MAX_REND_REQUEST_PER_MAINLOOP 16
-
 /** What is the threshold of in-progress (CIRCUIT_PURPOSE_S_CONNECT_REND)
  * rendezvous responses above which we won't launch new low-effort rendezvous
  * responses? (Intro2 cells with suitable PoW effort are not affected
@@ -767,7 +755,6 @@ rend_pqueue_clear(hs_pow_service_state_t *pow_state)
 static void
 handle_rend_pqueue_cb(mainloop_event_t *ev, void *arg)
 {
-  int count = 0;
   hs_service_t *service = arg;
   hs_pow_service_state_t *pow_state = service->state.pow_state;
   time_t now = time(NULL);
@@ -845,10 +832,6 @@ handle_rend_pqueue_cb(mainloop_event_t *ev, void *arg)
         token_bucket_ctr_get(&pow_state->pqueue_bucket) < 1) {
       break;
     }
-
-    if (++count == MAX_REND_REQUEST_PER_MAINLOOP) {
-      break;
-    }
   }
 
   /* If there are still some pending rendezvous circuits in the pqueue then
@@ -856,12 +839,8 @@ handle_rend_pqueue_cb(mainloop_event_t *ev, void *arg)
   if (smartlist_len(pow_state->rend_request_pqueue) > 0) {
     mainloop_event_activate(pow_state->pop_pqueue_ev);
 
-    // XXX: Is this a good threshhold to decide that we have a significant
-    // queue? I just made it up.
-    if (smartlist_len(pow_state->rend_request_pqueue) >
-        2*MAX_REND_REQUEST_PER_MAINLOOP) {
-      /* Note the fact that we had multiple eventloops worth of queue
-       * to service, for effort estimation */
+    if (smartlist_len(pow_state->rend_request_pqueue) >=
+        pow_state->pqueue_low_level) {
       pow_state->had_queue = 1;
     }
   }
@@ -922,7 +901,7 @@ enqueue_rend_request(const hs_service_t *service, hs_service_intro_point_t *ip,
 
   /* See if there are so many cells queued that we need to cull. */
   if (smartlist_len(pow_state->rend_request_pqueue) >=
-        QUEUED_REND_REQUEST_HIGH_WATER) {
+        pow_state->pqueue_high_level) {
     trim_rend_pqueue(pow_state, now);
     hs_metrics_pow_pqueue_rdv(service,
                               smartlist_len(pow_state->rend_request_pqueue));
index 357f527c343b492411057f3d41004045a093dea1..23c05419a65457e76ed80295280966c0cb4d9bba 100644 (file)
@@ -74,9 +74,16 @@ typedef struct hs_pow_service_state_t {
    * based on the amount of effort that was exerted in the PoW. */
   smartlist_t *rend_request_pqueue;
 
-  /* HRPR TODO Is this cursed? Including compat_libevent for this. feb 24 */
-  /* When PoW defenses are enabled, this event pops rendezvous requests from
-   * the service's priority queue; higher effort is higher priority. */
+  /* Low level mark for pqueue size. Below this length it's considered to be
+   * effectively empty when calculating effort adjustments. */
+  int pqueue_low_level;
+
+  /* High level mark for pqueue size. When the queue is this length we will
+   * trim it down to pqueue_high_level/2. */
+  int pqueue_high_level;
+
+  /* Event callback for dequeueing rend requests, paused when the queue is
+   * empty or rate limited. */
   mainloop_event_t *pop_pqueue_ev;
 
   /* Token bucket for rate limiting the priority queue */
index 43598ad7681e63d0eeb6aa506a7bd52fb8e9d240..b2af8815970977d52d812990b2ef73909f8bdade 100644 (file)
@@ -278,6 +278,12 @@ initialize_pow_defenses(hs_service_t *service)
   pow_state->rend_request_pqueue = smartlist_new();
   pow_state->pop_pqueue_ev = NULL;
 
+  /* If we are using the pqueue rate limiter, calculate min and max queue
+   * levels based on those programmed rates. If not, we have generic
+   * defaults */
+  pow_state->pqueue_low_level = 16;
+  pow_state->pqueue_high_level = 16384;
+
   if (service->config.pow_queue_rate > 0 &&
       service->config.pow_queue_burst >= service->config.pow_queue_rate) {
     pow_state->using_pqueue_bucket = 1;
@@ -285,6 +291,11 @@ initialize_pow_defenses(hs_service_t *service)
                           service->config.pow_queue_rate,
                           service->config.pow_queue_burst,
                           (uint32_t) approx_time());
+
+    pow_state->pqueue_low_level = MAX(8, service->config.pow_queue_rate / 4);
+    pow_state->pqueue_high_level =
+      service->config.pow_queue_burst +
+      service->config.pow_queue_rate * MAX_REND_TIMEOUT * 2;
   }
 
   /* We recalculate and update the suggested effort every HS_UPDATE_PERIOD
@@ -2701,7 +2712,8 @@ update_suggested_effort(hs_service_t *service, time_t now)
       /* Increase when the top of queue is high-effort */
       aimd_event = INCREASE;
     }
-  } else if (smartlist_len(pow_state->rend_request_pqueue) == 0) {
+  } else if (smartlist_len(pow_state->rend_request_pqueue) <
+             pow_state->pqueue_low_level) {
     /* Dec when the queue is empty now and had_queue wasn't set this period */
     aimd_event = DECREASE;
   }