Commit d357b97b authored by Nick Mathewson's avatar Nick Mathewson 🥔
Browse files

Merge remote-tracking branch 'mikeperry/bug7691-rebased'

parents dab25eb3 d05ff310
Loading
Loading
Loading
Loading

changes/bug7341

0 → 100644
+7 −0
Original line number Diff line number Diff line

 o Minor features:
   - Improve circuit build timeout handling for hidden services.
     In particular: adjust build timeouts more accurately depending
     upon the number of hop-RTTs that a particular circuit type
     undergoes. Additionally, launch intro circuits in parallel
     if they timeout, and take the first one to reply as valid.
+182 −3
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@
#include "routerparse.h"
#include "routerset.h"
#include "crypto.h"
#include "connection_edge.h"

#ifndef MIN
#define MIN(a,b) ((a)<(b)?(a):(b))
@@ -1503,6 +1504,171 @@ pathbias_count_build_success(origin_circuit_t *circ)
  }
}

/**
 * Send a probe down a circuit that the client attempted to use,
 * but for which the stream timed out/failed. The probe is a
 * RELAY_BEGIN cell with a 0.a.b.c destination address, which
 * the exit will reject and reply back, echoing that address.
 *
 * The reason for such probes is because it is possible to bias
 * a user's paths simply by causing timeouts, and these timeouts
 * are not possible to differentiate from unresponsive servers.
 *
 * The probe is sent at the end of the circuit lifetime for two
 * reasons: to prevent cyptographic taggers from being able to
 * drop cells to cause timeouts, and to prevent easy recognition
 * of probes before any real client traffic happens.
 *
 * Returns -1 if we couldn't probe, 0 otherwise.
 */
static int
pathbias_send_usable_probe(circuit_t *circ)
{
  /* Based on connection_ap_handshake_send_begin() */
  char payload[CELL_PAYLOAD_SIZE];
  int payload_len;
  origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);
  crypt_path_t *cpath_layer = NULL;
  char *probe_nonce = NULL;

  tor_assert(ocirc);

  cpath_layer = ocirc->cpath->prev;

  if (cpath_layer->state != CPATH_STATE_OPEN) {
    /* This can happen for cannibalized circuits. Their
     * last hop isn't yet open */
    log_info(LD_CIRC,
             "Got pathbias probe request for unopened circuit %d. "
             "Opened %d, len %d", ocirc->global_identifier,
             ocirc->has_opened, ocirc->build_state->desired_path_len);
    return -1;
  }

  /* We already went down this road. */
  if (circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING &&
      ocirc->pathbias_probe_id) {
    log_info(LD_CIRC,
             "Got pathbias probe request for circuit %d with "
             "outstanding probe", ocirc->global_identifier);
    return -1;
  }

  circuit_change_purpose(circ, CIRCUIT_PURPOSE_PATH_BIAS_TESTING);

  /* Update timestamp for circuit_expire_building to kill us */
  tor_gettimeofday(&circ->timestamp_began);

  /* Generate a random address for the nonce */
  crypto_rand((char*)&ocirc->pathbias_probe_nonce,
              sizeof(ocirc->pathbias_probe_nonce));
  ocirc->pathbias_probe_nonce &= 0x00ffffff;
  probe_nonce = tor_dup_ip(ocirc->pathbias_probe_nonce);

  tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce);
  payload_len = (int)strlen(payload)+1;

  // XXX: need this? Can we assume ipv4 will always be supported?
  // If not, how do we tell?
  //if (payload_len <= RELAY_PAYLOAD_SIZE - 4 && edge_conn->begincell_flags) {
  //  set_uint32(payload + payload_len, htonl(edge_conn->begincell_flags));
  //  payload_len += 4;
  //}

  /* Generate+Store stream id, make sure it's non-zero */
  ocirc->pathbias_probe_id = get_unique_stream_id_by_circ(ocirc);

  if (ocirc->pathbias_probe_id==0) {
    log_warn(LD_CIRC,
             "Ran out of stream IDs on circuit %u during "
             "pathbias probe attempt.", ocirc->global_identifier);
    tor_free(probe_nonce);
    return -1;
  }

  log_info(LD_CIRC,
           "Sending pathbias testing cell to %s:25 on stream %d for circ %d.",
           probe_nonce, ocirc->pathbias_probe_id, ocirc->global_identifier);
  tor_free(probe_nonce);

  /* Send a test relay cell */
  if (relay_send_command_from_edge(ocirc->pathbias_probe_id, circ,
                               RELAY_COMMAND_BEGIN, payload,
                               payload_len, cpath_layer) < 0) {
    log_notice(LD_CIRC,
             "Failed to send pathbias probe cell on circuit %d.",
             ocirc->global_identifier);
    return -1;
  }

  /* Mark it freshly dirty so it doesn't get expired in the meantime */
  circ->timestamp_dirty = time(NULL);

  return 0;
}

/**
 * Check the response to a pathbias probe, to ensure the
 * cell is recognized and the nonce and other probe
 * characteristics are as expected.
 *
 * If the response is valid, return 0. Otherwise return < 0.
 */
int
pathbias_check_probe_response(circuit_t *circ, const cell_t *cell)
{
  /* Based on connection_edge_process_relay_cell() */
  relay_header_t rh;
  int reason;
  uint32_t ipv4_host;
  origin_circuit_t *ocirc = TO_ORIGIN_CIRCUIT(circ);

  tor_assert(cell);
  tor_assert(ocirc);
  tor_assert(circ->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING);

  relay_header_unpack(&rh, cell->payload);

  reason = rh.length > 0 ?
        get_uint8(cell->payload+RELAY_HEADER_SIZE) : END_STREAM_REASON_MISC;

  if (rh.command == RELAY_COMMAND_END &&
      reason == END_STREAM_REASON_EXITPOLICY &&
      ocirc->pathbias_probe_id == rh.stream_id) {

    /* Check length+extract host: It is in network order after the reason code.
     * See connection_edge_end(). */
    if (rh.length < 9) { /* reason+ipv4+dns_ttl */
      log_notice(LD_PROTOCOL,
             "Short path bias probe response length field (%d).", rh.length);
      return - END_CIRC_REASON_TORPROTOCOL;
    }

    ipv4_host = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1));

    /* Check nonce */
    if (ipv4_host == ocirc->pathbias_probe_nonce) {
      ocirc->path_state = PATH_STATE_USE_SUCCEEDED;
      circuit_mark_for_close(circ, END_CIRC_REASON_FINISHED);
      log_info(LD_CIRC,
               "Got valid path bias probe back for circ %d, stream %d.",
               ocirc->global_identifier, ocirc->pathbias_probe_id);
      return 0;
    } else {
      log_notice(LD_CIRC,
               "Got strange probe value 0x%x vs 0x%x back for circ %d, "
               "stream %d.", ipv4_host, ocirc->pathbias_probe_nonce,
               ocirc->global_identifier, ocirc->pathbias_probe_id);
      return -1;
    }
  }
  log_info(LD_CIRC,
             "Got another cell back back on pathbias probe circuit %d: "
             "Command: %d, Reason: %d, Stream-id: %d",
             ocirc->global_identifier, rh.command, reason, rh.stream_id);
  return -1;
}

/**
 * Check if a circuit was used and/or closed successfully.
 *
@@ -1512,18 +1678,26 @@ pathbias_count_build_success(origin_circuit_t *circ)
 *
 * If we *have* successfully used the circuit, or it appears to
 * have been closed by us locally, count it as a success.
 *
 * Returns 0 if we're done making decisions with the circ,
 * or -1 if we want to probe it first.
 */
void
int
pathbias_check_close(origin_circuit_t *ocirc, int reason)
{
  circuit_t *circ = &ocirc->base_;

  if (!pathbias_should_count(ocirc)) {
    return;
    return 0;
  }

  if (ocirc->path_state == PATH_STATE_BUILD_SUCCEEDED) {
    if (circ->timestamp_dirty) {
      if (pathbias_send_usable_probe(circ) == 0)
        return -1;
      else
        pathbias_count_unusable(ocirc);

      /* Any circuit where there were attempted streams but no successful
       * streams could be bias */
      log_info(LD_CIRC,
@@ -1533,7 +1707,7 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason)
            reason, circ->purpose, ocirc->has_opened,
            circuit_state_to_string(circ->state),
            ocirc->build_state->desired_path_len);
      pathbias_count_unusable(ocirc);

    } else {
      if (reason & END_CIRC_REASON_FLAG_REMOTE) {
        /* Unused remote circ close reasons all could be bias */
@@ -1569,6 +1743,8 @@ pathbias_check_close(origin_circuit_t *ocirc, int reason)
  } else if (ocirc->path_state == PATH_STATE_USE_SUCCEEDED) {
    pathbias_count_successful_close(ocirc);
  }

  return 0;
}

/**
@@ -2567,6 +2743,9 @@ circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *exit)
{
  int err_reason = 0;
  warn_if_last_router_excluded(circ, exit);

  tor_gettimeofday(&circ->base_.timestamp_began);

  circuit_append_new_exit(circ, exit);
  circuit_set_state(TO_CIRCUIT(circ), CIRCUIT_STATE_BUILDING);
  if ((err_reason = circuit_send_next_onion_skin(circ))<0) {
+2 −1
Original line number Diff line number Diff line
@@ -60,7 +60,8 @@ const node_t *choose_good_entry_server(uint8_t purpose,
double pathbias_get_extreme_rate(const or_options_t *options);
int pathbias_get_dropguards(const or_options_t *options);
void pathbias_count_timeout(origin_circuit_t *circ);
void pathbias_check_close(origin_circuit_t *circ, int reason);
int pathbias_check_close(origin_circuit_t *circ, int reason);
int pathbias_check_probe_response(circuit_t *circ, const cell_t *cell);

#endif
+7 −1
Original line number Diff line number Diff line
@@ -414,6 +414,8 @@ circuit_purpose_to_controller_string(uint8_t purpose)
      return "MEASURE_TIMEOUT";
    case CIRCUIT_PURPOSE_CONTROLLER:
      return "CONTROLLER";
    case CIRCUIT_PURPOSE_PATH_BIAS_TESTING:
      return "PATH_BIAS_TESTING";

    default:
      tor_snprintf(buf, sizeof(buf), "UNKNOWN_%d", (int)purpose);
@@ -441,6 +443,7 @@ circuit_purpose_to_controller_hs_state_string(uint8_t purpose)
    case CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT:
    case CIRCUIT_PURPOSE_TESTING:
    case CIRCUIT_PURPOSE_CONTROLLER:
    case CIRCUIT_PURPOSE_PATH_BIAS_TESTING:
      return NULL;

    case CIRCUIT_PURPOSE_INTRO_POINT:
@@ -1356,7 +1359,10 @@ circuit_mark_for_close_(circuit_t *circ, int reason, int line,
  }

  if (CIRCUIT_IS_ORIGIN(circ)) {
    pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason);
    if (pathbias_check_close(TO_ORIGIN_CIRCUIT(circ), reason) == -1) {
      /* Don't close it yet, we need to test it first */
      return;
    }

    /* We don't send reasons when closing circuits at the origin. */
    reason = END_CIRC_REASON_NONE;
+118 −51
Original line number Diff line number Diff line
@@ -280,17 +280,19 @@ circuit_get_best(const entry_connection_t *conn,
    if (!CIRCUIT_IS_ORIGIN(circ))
      continue;
    origin_circ = TO_ORIGIN_CIRCUIT(circ);
    if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose,
                               need_uptime,need_internal,now.tv_sec))
      continue;

    /* Log an info message if we're going to launch a new intro circ in
     * parallel */
    if (purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT &&
        !must_be_open && circ->state != CIRCUIT_STATE_OPEN &&
        tv_mdiff(&now, &circ->timestamp_began) > circ_times.timeout_ms) {
        !must_be_open && origin_circ->hs_circ_has_timed_out) {
        intro_going_on_but_too_old = 1;
        continue;
    }

    if (!circuit_is_acceptable(origin_circ,conn,must_be_open,purpose,
                               need_uptime,need_internal,now.tv_sec))
      continue;

    /* now this is an acceptable circ to hand back. but that doesn't
     * mean it's the *best* circ to hand back. try to decide.
     */
@@ -367,8 +369,8 @@ circuit_expire_building(void)
   * circuit_build_times_get_initial_timeout() if we haven't computed
   * custom timeouts yet */
  struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff,
    cannibalize_cutoff, close_cutoff, extremely_old_cutoff,
    hs_extremely_old_cutoff;
    close_cutoff, extremely_old_cutoff, hs_extremely_old_cutoff,
    cannibalized_cutoff, c_intro_cutoff, s_intro_cutoff, stream_cutoff;
  const or_options_t *options = get_options();
  struct timeval now;
  cpath_build_state_t *build_state;
@@ -407,10 +409,60 @@ circuit_expire_building(void)
    timersub(&now, &diff, &target);                         \
  } while (0)

  /**
   * Because circuit build timeout is calculated only based on 3 hop
   * general purpose circuit construction, we need to scale the timeout
   * to make it properly apply to longer circuits, and circuits of
   * certain usage types. The following diagram illustrates how we
   * derive the scaling below. In short, we calculate the number
   * of times our telescoping-based circuit construction causes cells
   * to traverse each link for the circuit purpose types in question,
   * and then assume each link is equivalent.
   *
   * OP --a--> A --b--> B --c--> C
   * OP --a--> A --b--> B --c--> C --d--> D
   *
   * Let h = a = b = c = d
   *
   * Three hops (general_cutoff)
   *   RTTs = 3a + 2b + c
   *   RTTs = 6h
   * Cannibalized:
   *   RTTs = a+b+c+d
   *   RTTs = 4h
   * Four hops:
   *   RTTs = 4a + 3b + 2c + d
   *   RTTs = 10h
   * Client INTRODUCE1+ACK: // XXX: correct?
   *   RTTs = 5a + 4b + 3c + 2d
   *   RTTs = 14h
   * Server intro:
   *   RTTs = 4a + 3b + 2c
   *   RTTs = 9h
   */
  SET_CUTOFF(general_cutoff, circ_times.timeout_ms);
  SET_CUTOFF(begindir_cutoff, circ_times.timeout_ms);
  SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (4/3.0));
  SET_CUTOFF(cannibalize_cutoff, circ_times.timeout_ms / 2.0);

  /* > 3hop circs seem to have a 1.0 second delay on their cannibalized
   * 4th hop. */
  SET_CUTOFF(fourhop_cutoff, circ_times.timeout_ms * (10/6.0) + 1000);

  /* CIRCUIT_PURPOSE_C_ESTABLISH_REND behaves more like a RELAY cell.
   * Use the stream cutoff (more or less). */
  SET_CUTOFF(stream_cutoff, MAX(options->CircuitStreamTimeout,15)*1000 + 1000);

  /* Be lenient with cannibalized circs. They already survived the official
   * CBT, and they're usually not perf-critical. */
  SET_CUTOFF(cannibalized_cutoff,
             MAX(circ_times.close_ms*(4/6.0),
                 options->CircuitStreamTimeout * 1000) + 1000);

  // Intro circs have an extra round trip (and are also 4 hops long)
  SET_CUTOFF(c_intro_cutoff, circ_times.timeout_ms * (14/6.0) + 1000);

  // Server intro circs have an extra round trip
  SET_CUTOFF(s_intro_cutoff, circ_times.timeout_ms * (9/6.0) + 1000);

  SET_CUTOFF(close_cutoff, circ_times.close_ms);
  SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000);

@@ -441,13 +493,22 @@ circuit_expire_building(void)
    build_state = TO_ORIGIN_CIRCUIT(victim)->build_state;
    if (build_state && build_state->onehop_tunnel)
      cutoff = begindir_cutoff;
    else if (build_state && build_state->desired_path_len == 4
             && !TO_ORIGIN_CIRCUIT(victim)->has_opened)
      cutoff = fourhop_cutoff;
    else if (TO_ORIGIN_CIRCUIT(victim)->has_opened)
      cutoff = cannibalize_cutoff;
    else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
      cutoff = close_cutoff;
    else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING ||
             victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT)
      cutoff = c_intro_cutoff;
    else if (victim->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO)
      cutoff = s_intro_cutoff;
    else if (victim->purpose == CIRCUIT_PURPOSE_C_ESTABLISH_REND)
      cutoff = stream_cutoff;
    else if (victim->purpose == CIRCUIT_PURPOSE_PATH_BIAS_TESTING)
      cutoff = close_cutoff;
    else if (TO_ORIGIN_CIRCUIT(victim)->has_opened &&
             victim->state != CIRCUIT_STATE_OPEN)
      cutoff = cannibalized_cutoff;
    else if (build_state && build_state->desired_path_len >= 4)
      cutoff = fourhop_cutoff;
    else
      cutoff = general_cutoff;

@@ -520,8 +581,6 @@ circuit_expire_building(void)
        default: /* most open circuits can be left alone. */
          continue; /* yes, continue inside a switch refers to the nearest
                     * enclosing loop. C is smart. */
        case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
        case CIRCUIT_PURPOSE_C_INTRODUCING:
        case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
          break; /* too old, need to die */
        case CIRCUIT_PURPOSE_C_REND_READY:
@@ -533,6 +592,19 @@ circuit_expire_building(void)
              victim->timestamp_dirty > cutoff.tv_sec)
            continue;
          break;
        case CIRCUIT_PURPOSE_PATH_BIAS_TESTING:
          /* Open path bias testing circuits are given a long
           * time to complete the test, but not forever */
          TO_ORIGIN_CIRCUIT(victim)->path_state = PATH_STATE_USE_FAILED;
          break;
        case CIRCUIT_PURPOSE_C_INTRODUCING:
          /* We keep old introducing circuits around for
           * a while in parallel, and they can end up "opened".
           * We decide below if we're going to mark them timed
           * out and eventually close them.
           */
          break;
        case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
        case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
        case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
          /* rend and intro circs become dirty each time they
@@ -596,6 +668,18 @@ circuit_expire_building(void)
          circuit_build_times_set_timeout(&circ_times);
        }
      }

      if (TO_ORIGIN_CIRCUIT(victim)->has_opened &&
          victim->purpose != CIRCUIT_PURPOSE_PATH_BIAS_TESTING) {
        /* For path bias: we want to let these guys live for a while
         * so we get a chance to test them. */
        log_info(LD_CIRC,
                 "Allowing cannibalized circuit %d time to finish building "
                 "as a pathbias testing circ.",
                 TO_ORIGIN_CIRCUIT(victim)->global_identifier);
        circuit_change_purpose(victim, CIRCUIT_PURPOSE_PATH_BIAS_TESTING);
        continue; /* It now should have a longer timeout next time */
      }
    }

    /* If this is a hidden service client circuit which is far enough
@@ -621,6 +705,9 @@ circuit_expire_building(void)
        if (TO_ORIGIN_CIRCUIT(victim)->build_state->pending_final_cpath ==
            NULL)
          break;
        /* fallthrough! */
      case CIRCUIT_PURPOSE_C_INTRODUCING:
        /* connection_ap_handshake_attach_circuit() will relaunch for us */
      case CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT:
      case CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED:
        /* If we have reached this line, we want to spare the circ for now. */
@@ -653,15 +740,23 @@ circuit_expire_building(void)
    }

    if (victim->n_chan)
      log_info(LD_CIRC,"Abandoning circ %s:%d (state %d:%s, purpose %d)",
      log_info(LD_CIRC,
               "Abandoning circ %u %s:%d (state %d,%d:%s, purpose %d, "
               "len %d)", TO_ORIGIN_CIRCUIT(victim)->global_identifier,
               channel_get_canonical_remote_descr(victim->n_chan),
               victim->n_circ_id,
               TO_ORIGIN_CIRCUIT(victim)->has_opened,
               victim->state, circuit_state_to_string(victim->state),
               victim->purpose);
               victim->purpose,
               TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len);
    else
      log_info(LD_CIRC,"Abandoning circ %d (state %d:%s, purpose %d)",
               victim->n_circ_id, victim->state,
               circuit_state_to_string(victim->state), victim->purpose);
      log_info(LD_CIRC,
               "Abandoning circ %u %d (state %d,%d:%s, purpose %d, len %d)",
               TO_ORIGIN_CIRCUIT(victim)->global_identifier,
               victim->n_circ_id, TO_ORIGIN_CIRCUIT(victim)->has_opened,
               victim->state,
               circuit_state_to_string(victim->state), victim->purpose,
               TO_ORIGIN_CIRCUIT(victim)->build_state->desired_path_len);

    circuit_log_path(LOG_INFO,LD_CIRC,TO_ORIGIN_CIRCUIT(victim));
    if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
@@ -1165,18 +1260,6 @@ circuit_has_opened(origin_circuit_t *circ)
{
  control_event_circuit_status(circ, CIRC_EVENT_BUILT, 0);

  /* Cannibalized circuits count as used for path bias.
   * (PURPOSE_GENERAL circs especially, since they are
   * marked dirty and often go unused after preemptive
   * building). */
  // XXX: Cannibalized now use RELAY_EARLY, which is visible
  // to taggers end-to-end! We really need to probe these instead.
  // Don't forget to remove this check once that's done!
  if (circ->has_opened &&
      circ->build_state->desired_path_len > DEFAULT_ROUTE_LEN) {
    circ->path_state = PATH_STATE_USE_SUCCEEDED;
  }

  /* Remember that this circuit has finished building. Now if we start
   * it building again later (e.g. by extending it), we will know not
   * to consider its build time. */
@@ -2101,28 +2184,12 @@ connection_ap_handshake_attach_circuit(entry_connection_t *conn)

    if (retval > 0) {
      /* one has already sent the intro. keep waiting. */
      circuit_t *c = NULL;
      tor_assert(introcirc);
      log_info(LD_REND, "Intro circ %d present and awaiting ack (rend %d). "
               "Stalling. (stream %d sec old)",
               introcirc->base_.n_circ_id,
               rendcirc ? rendcirc->base_.n_circ_id : 0,
               conn_age);
      /* abort parallel intro circs, if any */
      for (c = global_circuitlist; c; c = c->next) {
        if (c->purpose == CIRCUIT_PURPOSE_C_INTRODUCING &&
            !c->marked_for_close && CIRCUIT_IS_ORIGIN(c)) {
          origin_circuit_t *oc = TO_ORIGIN_CIRCUIT(c);
          if (oc->rend_data &&
              !rend_cmp_service_ids(
                            ENTRY_TO_EDGE_CONN(conn)->rend_data->onion_address,
                            oc->rend_data->onion_address)) {
            log_info(LD_REND|LD_CIRC, "Closing introduction circuit that we "
                     "built in parallel.");
            circuit_mark_for_close(c, END_CIRC_REASON_TIMEOUT);
          }
        }
      }
      return 0;
    }

Loading