Commit 15fdfc29 authored by Mike Perry's avatar Mike Perry
Browse files

Bug 7691: Send a probe cell down certain types of circs.

In general, if we tried to use a circ for a stream, but then decided to place
that stream on a different circuit, we need to probe the original circuit
before deciding it was a "success".

We also need to do the same for cannibalized circuits that go unused.
parent 3458d904
Loading
Loading
Loading
Loading
+157 −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,149 @@ pathbias_count_build_success(origin_circuit_t *circ)
  }
}

/**
 * Send a probe down a circuit that wasn't usable.
 *
 * 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;
  // XXX: Generate a random 0.a.b.c adddress
  const char *probe_nonce = "0.1.2.3";

  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);

  tor_snprintf(payload,RELAY_PAYLOAD_SIZE, "%s:25", probe_nonce);
  tor_addr_parse(&ocirc->pathbias_probe_nonce, 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);
    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);

  /* 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.
 *
 * If the response is valid, return 0. Otherwise return < 0.
 */
int
pathbias_check_probe_response(circuit_t *circ, cell_t *cell)
{
  /* Based on connection_edge_process_relay_cell() */
  relay_header_t rh;
  int reason;
  uint32_t ipv4_host;
  tor_addr_t 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_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
             "Path bias probe response length field is insane (%d).",
             rh.length);
      return - END_CIRC_REASON_TORPROTOCOL;
    }

    ipv4_host = get_uint32(cell->payload+RELAY_HEADER_SIZE+1);
    tor_addr_from_ipv4n(&host, ipv4_host);

    /* Check nonce */
    if (memcmp(&host, &ocirc->pathbias_probe_nonce, sizeof(tor_addr_t)) == 0) {
      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;
    }
  }
  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 +1656,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 +1685,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 +1721,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;
}

/**
+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, 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;
+19 −12
Original line number Diff line number Diff line
@@ -493,6 +493,8 @@ circuit_expire_building(void)
      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;
@@ -581,6 +583,11 @@ 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".
@@ -652,6 +659,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
@@ -1232,18 +1251,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. */
+1 −1
Original line number Diff line number Diff line
@@ -1661,7 +1661,7 @@ connection_ap_process_natd(entry_connection_t *conn)
/** Iterate over the two bytes of stream_id until we get one that is not
 * already in use; return it. Return 0 if can't get a unique stream_id.
 */
static streamid_t
streamid_t
get_unique_stream_id_by_circ(origin_circuit_t *circ)
{
  edge_connection_t *tmpconn;
Loading