Commit 37088869 authored by Nick Mathewson's avatar Nick Mathewson 🐘
Browse files

Implement RESOLVE/RESOLVED cells and socks resolve code


svn:r1978
parent dfaa5ce7
Loading
Loading
Loading
Loading
+16 −5
Changes for src/or/buffers.c: 16 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -409,6 +409,9 @@ int fetch_from_buf_http(buf_t *buf,
  return 1;
}

#define SOCKS_COMMAND_CONNECT 0x01
#define SOCKS_COMMAND_RESOLVE 0xF0

/** There is a (possibly incomplete) socks handshake on <b>buf</b>, of one
 * of the forms
 *  - socks4: "socksheader username\\0"
@@ -467,8 +470,12 @@ int fetch_from_buf_socks(buf_t *buf, socks_request_t *req) {
      log_fn(LOG_DEBUG,"socks5: checking request");
      if(buf->datalen < 8) /* basic info plus >=2 for addr plus 2 for port */
        return 0; /* not yet */
      if(*(buf->mem+1) != 1) { /* not a connect? we don't support it. */
        log_fn(LOG_WARN,"socks5: command %d not '1'. Rejecting.",*(buf->mem+1));
      req->command = (unsigned char) *(buf->mem+1);
      if(req->command != SOCKS_COMMAND_CONNECT &&
         req->command != SOCKS_COMMAND_RESOLVE) {
        /* not a connect or resolve? we don't support it. */
        log_fn(LOG_WARN,"socks5: command %d not recognized. Rejecting.",
               req->command);
        return -1;
      }
      switch(*(buf->mem+3)) { /* address type */
@@ -516,14 +523,18 @@ int fetch_from_buf_socks(buf_t *buf, socks_request_t *req) {
      if(buf->datalen < SOCKS4_NETWORK_LEN) /* basic info available? */
        return 0; /* not yet */

      if(*(buf->mem+1) != 1) { /* not a connect? we don't support it. */
        log_fn(LOG_WARN,"socks4: command %d not '1'. Rejecting.",*(buf->mem+1));
      req->command = (unsigned char) *(buf->mem+1);
      if(req->command != SOCKS_COMMAND_CONNECT &&
         req->command != SOCKS_COMMAND_RESOLVE) {
        /* not a connect or resolve? we don't support it. */
        log_fn(LOG_WARN,"socks4: command %d not recognized. Rejecting.",
               req->command);
        return -1;
      }

      req->port = ntohs(*(uint16_t*)(buf->mem+2));
      destip = ntohl(*(uint32_t*)(buf->mem+4));
      if(!req->port || !destip) {
      if((!req->port && req->command!=SOCKS_COMMAND_RESOLVE) || !destip) {
        log_fn(LOG_WARN,"socks4: Port or DestIP is zero. Rejecting.");
        return -1;
      }
+16 −3
Changes for src/or/circuituse.c: 16 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -73,7 +73,14 @@ static int circuit_is_acceptable(circuit_t *circ,
      return 0; /* this circuit is screwed and doesn't know it yet */
    }

    if(purpose == CIRCUIT_PURPOSE_C_GENERAL) {
    if (conn->socks_request &&
        conn->socks_request->command == SOCKS_COMMAND_RESOLVE) {
      /* 0.0.7 servers and earlier don't support DNS resolution.  There are no
       * ORs running code before 0.0.7, so we only worry about 0.0.7.  Once all
       * servers are running 0.0.8, remove this check. */
      if (!strncmp(exitrouter->platform, "Tor 0.0.7", 9))
        return 0;
    } else if(purpose == CIRCUIT_PURPOSE_C_GENERAL) {
      if(connection_ap_can_use_exit(conn, exitrouter) == ADDR_POLICY_REJECTED) {
        /* can't exit from this router */
        return 0;
@@ -618,10 +625,12 @@ circuit_get_open_circ_or_launch(connection_t *conn,
                                circuit_t **circp) {
  circuit_t *circ;
  uint32_t addr;
  int is_resolve;

  tor_assert(conn);
  tor_assert(circp);
  tor_assert(conn->state == AP_CONN_STATE_CIRCUIT_WAIT);
  is_resolve = conn->socks_request->command == SOCKS_COMMAND_RESOLVE;

  circ = circuit_get_best(conn, 1, desired_circuit_purpose);

@@ -630,7 +639,8 @@ circuit_get_open_circ_or_launch(connection_t *conn,
    return 1; /* we're happy */
  }

  if(!connection_edge_is_rendezvous_stream(conn)) { /* general purpose circ */
  /* Do we need to check exit policy? */
  if(!is_resolve && !connection_edge_is_rendezvous_stream(conn)) {
    addr = client_dns_lookup_entry(conn->socks_request->address);
    if(router_exit_policy_all_routers_reject(addr, conn->socks_request->port)) {
      log_fn(LOG_WARN,"No Tor server exists that allows exit to %s:%d. Rejecting.",
@@ -742,10 +752,13 @@ int connection_ap_handshake_attach_circuit(connection_t *conn) {
      circ->timestamp_dirty = time(NULL);

    link_apconn_to_circ(conn, circ);
    tor_assert(conn->socks_request);
    if (conn->socks_request->command == SOCKS_COMMAND_CONNECT)
      connection_ap_handshake_send_begin(conn, circ);
    else
      connection_ap_handshake_send_resolve(conn, circ);

    return 1;

  } else { /* we're a rendezvous conn */
    circuit_t *rendcirc=NULL, *introcirc=NULL;

+4 −1
Changes for src/or/connection.c: 4 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -1276,7 +1276,10 @@ void assert_connection_ok(connection_t *conn, time_t now)
  } else {
    tor_assert(!conn->socks_request);
  }
  if(conn->type != CONN_TYPE_DIR) {
  if (conn->type == CONN_TYPE_EXIT) {
    tor_assert(conn->purpose == EXIT_PURPOSE_CONNECT ||
               conn->purpose == EXIT_PURPOSE_RESOLVE);
  } else if(conn->type != CONN_TYPE_DIR) {
    tor_assert(!conn->purpose); /* only used for dir types currently */
  }

+163 −0
Changes for src/or/connection_edge.c: 163 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -371,6 +371,23 @@ static int connection_ap_handshake_process_socks(connection_t *conn) {
    return sockshere;
  } /* else socks handshake is done, continue processing */

  if (socks->command == SOCKS_COMMAND_RESOLVE) {
    /* Reply to resolves immediately if we can. */
    if (strlen(socks->address) > RELAY_PAYLOAD_SIZE) {
      connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_ERROR,0,NULL);
      conn->socks_request->has_finished = 1;
      connection_mark_for_close(conn);
    }
    uint32_t answer = htonl(client_dns_lookup_entry(socks->address));
    if (answer) {
      connection_ap_handshake_socks_resolved(conn,RESOLVED_TYPE_IPV4,4,
                                             (char*)&answer);
      conn->socks_request->has_finished = 1;
      connection_mark_for_close(conn);
      return 0;
    }
  }

  /* this call _modifies_ socks->address iff it's a hidden-service request */
  if (rend_parse_rendezvous_address(socks->address) < 0) {
    /* normal request */
@@ -487,6 +504,46 @@ int connection_ap_handshake_send_begin(connection_t *ap_conn, circuit_t *circ)
  return 0;
}

/** Write a relay resolve cell, using destaddr and destport from ap_conn's
 * socks_request field, and send it down circ.
 *
 * If ap_conn is broken, mark it for close and return -1. Else return 0.
 */
int connection_ap_handshake_send_resolve(connection_t *ap_conn, circuit_t *circ)
{
  int payload_len;
  const char *string_addr;

  tor_assert(ap_conn->type == CONN_TYPE_AP);
  tor_assert(ap_conn->state == AP_CONN_STATE_CIRCUIT_WAIT);
  tor_assert(ap_conn->socks_request);
  tor_assert(ap_conn->socks_request->command == SOCKS_COMMAND_RESOLVE);
  tor_assert(circ->purpose == CIRCUIT_PURPOSE_C_GENERAL);

  ap_conn->stream_id = get_unique_stream_id_by_circ(circ);
  if (ap_conn->stream_id==0) {
    /* Don't send end: there is no 'other side' yet */
    ap_conn->has_sent_end = 1;
    connection_mark_for_close(ap_conn);
    circuit_mark_for_close(circ);
    return -1;
  }

  string_addr = ap_conn->socks_request->address;
  payload_len = strlen(string_addr);
  tor_assert(strlen(string_addr) <= RELAY_PAYLOAD_SIZE);

  log_fn(LOG_DEBUG,"Sending relay cell to begin stream %d.",ap_conn->stream_id);

  if(connection_edge_send_command(ap_conn, circ, RELAY_COMMAND_RESOLVE,
                           string_addr, payload_len, ap_conn->cpath_layer) < 0)
    return -1; /* circuit is closed, don't continue */

  ap_conn->state = AP_CONN_STATE_RESOLVE_WAIT;
  log_fn(LOG_INFO,"Address sent for resolve, ap socket %d, n_circ_id %d",ap_conn->s,circ->n_circ_id);
  return 0;
}

/** Make an AP connection_t, do a socketpair and attach one side
 * to the conn, connection_add it, initialize it to circuit_wait,
 * and call connection_ap_handshake_attach_circuit(conn) on it.
@@ -544,6 +601,59 @@ int connection_ap_make_bridge(char *address, uint16_t port) {
  return fd[1];
}

void connection_ap_handshake_socks_resolved(connection_t *conn,
                                            int answer_type,
                                            int answer_len,
                                            const char *answer)
{
  char buf[256];
  int replylen;

  if (answer_type == RESOLVED_TYPE_IPV4) {
    uint32_t a = get_uint32(answer);
    client_dns_set_entry(conn->socks_request->address, ntohl(a));
  }

  if (conn->socks_request->socks_version == 4) {
    buf[0] = 0x00; /* version */
    if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) {
      buf[1] = 90; /* "Granted" */
      set_uint16(buf+2, 0);
      memcpy(buf+4, answer, 4); /* address */
      replylen = SOCKS4_NETWORK_LEN;
   } else {
      buf[1] = 91; /* "error" */
      memset(buf+2, 0, 6);
      replylen = SOCKS4_NETWORK_LEN;
    }
  } else {
    /* SOCKS5 */
    buf[0] = 0x05; /* version */
    if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) {
      buf[1] = 0; /* succeeded */
      buf[2] = 0; /* reserved */
      buf[3] = 0x01; /* IPv4 address type */
      memcpy(buf+4, answer, 4); /* address */
      set_uint16(buf+8, 0); /* port == 0. */
      replylen = 10;
    } else if (answer_type == RESOLVED_TYPE_IPV6 && answer_len == 16) {
      buf[1] = 0; /* succeeded */
      buf[2] = 0; /* reserved */
      buf[3] = 0x04; /* IPv6 address type */
      memcpy(buf+4, answer, 16); /* address */
      set_uint16(buf+20, 0); /* port == 0. */
      replylen = 22;
    } else {
      buf[1] = 0x04; /* host unreachable */
      memset(buf+2, 0, 8);
      replylen = 10;
    }
  }
  connection_ap_handshake_socks_reply(conn, buf, replylen,
                                      answer_type == RESOLVED_TYPE_IPV4 ||
                                      answer_type == RESOLVED_TYPE_IPV6);
}

/** Send a socks reply to stream <b>conn</b>, using the appropriate
 * socks version, etc.
 *
@@ -631,6 +741,7 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ) {

  log_fn(LOG_DEBUG,"Creating new exit connection.");
  n_stream = connection_new(CONN_TYPE_EXIT);
  n_stream->purpose = EXIT_PURPOSE_CONNECT;

  n_stream->stream_id = rh.stream_id;
  n_stream->port = atoi(colon+1);
@@ -694,6 +805,52 @@ int connection_exit_begin_conn(cell_t *cell, circuit_t *circ) {
  return 0;
}

/**
 * Called when we receive a RELAY_RESOLVE cell 'cell' along the circuit 'circ';
 * begin resolving the hostname, and (eventually) reply with a RESOLVED cell.
 */
int connection_exit_begin_resolve(cell_t *cell, circuit_t *circ) {
  connection_t *dummy_conn;
  relay_header_t rh;

  assert_circuit_ok(circ);
  relay_header_unpack(&rh, cell->payload);


  /* This 'dummy_conn' only exists to remember the stream ID
   * associated with the resolve request; and to make the
   * implementation of dns.c more uniform.  (We really only need to
   * remember the circuit, the stream ID, and the hostname to be
   * resolved; but if we didn't store them in a connection like this,
   * the housekeeping in dns.c would get way more complicated.)
   */
  dummy_conn = connection_new(CONN_TYPE_EXIT);
  dummy_conn->stream_id = rh.stream_id;
  dummy_conn->address = tor_strndup(cell->payload+RELAY_HEADER_SIZE,
                                    rh.length);
  dummy_conn->port = 0;
  dummy_conn->state = EXIT_CONN_STATE_RESOLVEFAILED;
  dummy_conn->purpose = EXIT_PURPOSE_RESOLVE;

  /* send it off to the gethostbyname farm */
  switch(dns_resolve(dummy_conn)) {
    case 1: /* resolve worked; resolved cell was sent. */
      connection_free(dummy_conn);
      return 0;
    case -1: /* resolve failed; resolved cell was sent. */
      log_fn(LOG_INFO,"Resolve failed (%s).",dummy_conn->address);
      connection_free(dummy_conn);
      break;
    case 0: /* resolve added to pending list */
      /* add it into the linked list of resolving_streams on this circuit */
      dummy_conn->next_stream = circ->resolving_streams;
      circ->resolving_streams = dummy_conn;
      assert_circuit_ok(circ);
      ;
  }
  return 0;
}

/** Connect to conn's specified addr and port. If it worked, conn
 * has now been added to the connection_array.
 *
@@ -776,6 +933,12 @@ int connection_ap_can_use_exit(connection_t *conn, routerinfo_t *exit)
  log_fn(LOG_DEBUG,"considering nickname %s, for address %s / port %d:",
         exit->nickname, conn->socks_request->address,
         conn->socks_request->port);
  if (conn->socks_request->command == SOCKS_COMMAND_RESOLVE) {
    /* 0.0.7 servers and earlier don't support DNS resolution.  There are no
     * ORs running code before 0.0.7, so we only worry about 0.0.7.  Once all
     * servers are running 0.0.8, remove this check. */
    return strncmp(exit->platform, "Tor 0.0.7", 9) ? 1 : 0;
  }
  addr = client_dns_lookup_entry(conn->socks_request->address);
  return router_compare_addr_to_exit_policy(addr,
           conn->socks_request->port, exit->exit_policy);
+54 −5
Changes for src/or/dns.c: 54 added lines, 5 removed lines.
Original line number Diff line number Diff line
@@ -71,6 +71,7 @@ static void dns_found_answer(char *address, uint32_t addr, char outcome);
static int dnsworker_main(void *data);
static int spawn_dnsworker(void);
static void spawn_enough_dnsworkers(void);
static void send_resolved_cell(connection_t *conn, uint8_t answer_type);

/** Splay tree of cached_resolve objects. */
static SPLAY_HEAD(cache_tree, cached_resolve) cache_root;
@@ -141,6 +142,34 @@ static void purge_expired_resolves(uint32_t now) {
  }
}

static void send_resolved_cell(connection_t *conn, uint8_t answer_type)
{
  char buf[RELAY_PAYLOAD_SIZE];
  int buflen;

  buf[0] = answer_type;

  switch (answer_type)
    {
    case RESOLVED_TYPE_IPV4:
      buf[1] = 4;
      set_uint32(buf+2, htonl(conn->addr));
      buflen = 6;
      break;
    case RESOLVED_TYPE_ERROR_TRANSIENT:
    case RESOLVED_TYPE_ERROR:
      buf[1] = 24; /* length of "error resolving hostname" */
      strcpy(buf+2, "error resolving hostname");
      buflen = 26;
      break;
    default:
      tor_assert(0);
    }
  connection_edge_send_command(conn, circuit_get_by_conn(conn),
                               RELAY_COMMAND_RESOLVED, buf, buflen,
                               conn->cpath_layer);
}

/** See if we have a cache entry for <b>exitconn</b>-\>address. if so,
 * if resolve valid, put it into <b>exitconn</b>-\>addr and return 1.
 * If resolve failed, return -1.
@@ -179,7 +208,8 @@ int dns_resolve(connection_t *exitconn) {
    switch(resolve->state) {
      case CACHE_STATE_PENDING:
        /* add us to the pending list */
        pending_connection = tor_malloc(sizeof(struct pending_connection_t));
        pending_connection = tor_malloc_zero(
                                      sizeof(struct pending_connection_t));
        pending_connection->conn = exitconn;
        pending_connection->next = resolve->pending_connections;
        resolve->pending_connections = pending_connection;
@@ -191,8 +221,12 @@ int dns_resolve(connection_t *exitconn) {
        exitconn->addr = resolve->addr;
        log_fn(LOG_DEBUG,"Connection (fd %d) found cached answer for '%s'",
               exitconn->s, exitconn->address);
        if (exitconn->purpose == EXIT_PURPOSE_RESOLVE)
          send_resolved_cell(exitconn, RESOLVED_TYPE_IPV4);
        return 1;
      case CACHE_STATE_FAILED:
        if (exitconn->purpose == EXIT_PURPOSE_RESOLVE)
          send_resolved_cell(exitconn, RESOLVED_TYPE_ERROR);
        return -1;
    }
    tor_assert(0);
@@ -205,7 +239,7 @@ int dns_resolve(connection_t *exitconn) {
  resolve->address[MAX_ADDRESSLEN-1] = 0;

  /* add us to the pending list */
  pending_connection = tor_malloc(sizeof(struct pending_connection_t));
  pending_connection = tor_malloc_zero(sizeof(struct pending_connection_t));
  pending_connection->conn = exitconn;
  pending_connection->next = NULL;
  resolve->pending_connections = pending_connection;
@@ -240,6 +274,7 @@ static int assign_to_dnsworker(connection_t *exitconn) {
  if(!dnsconn) {
    log_fn(LOG_WARN,"no idle dns workers. Failing.");
    dns_cancel_pending_resolve(exitconn->address);
    send_resolved_cell(exitconn, RESOLVED_TYPE_ERROR_TRANSIENT);
    return -1;
  }

@@ -453,21 +488,25 @@ static void dns_found_answer(char *address, uint32_t addr, char outcome) {
    pend = resolve->pending_connections;
    assert_connection_ok(pend->conn,time(NULL));
    pend->conn->addr = resolve->addr;

    if(resolve->state == CACHE_STATE_FAILED) {
    pendconn = pend->conn; /* don't pass complex things to the
                              connection_mark_for_close macro */

    if(resolve->state == CACHE_STATE_FAILED) {
      /* prevent double-remove. */
      pendconn->state = EXIT_CONN_STATE_RESOLVEFAILED;
      circuit_detach_stream(circuit_get_by_conn(pendconn), pendconn);
      if (pendconn->purpose == EXIT_PURPOSE_CONNECT)
        connection_edge_end(pendconn, END_STREAM_REASON_MISC, pendconn->cpath_layer);
      else
        send_resolved_cell(pendconn, RESOLVED_TYPE_ERROR);
      connection_free(pendconn);
    } else {
      if (pendconn->purpose == EXIT_PURPOSE_CONNECT) {
        /* prevent double-remove. */
        pend->conn->state = EXIT_CONN_STATE_CONNECTING;

        circ = circuit_get_by_conn(pend->conn);
      assert(circ);
        tor_assert(circ);
        /* unlink pend->conn from resolving_streams, */
        circuit_detach_stream(circ, pend->conn);
        /* and link it to n_streams */
@@ -475,6 +514,16 @@ static void dns_found_answer(char *address, uint32_t addr, char outcome) {
        circ->n_streams = pend->conn;

        connection_exit_connect(pend->conn);
      } else {
        /* prevent double-remove.  This isn't really an accurate state,
         * but it does the right thing. */
        pendconn->state = EXIT_CONN_STATE_RESOLVEFAILED;
        send_resolved_cell(pendconn, RESOLVED_TYPE_IPV4);
        circ = circuit_get_by_conn(pendconn);
        tor_assert(circ);
        circuit_detach_stream(circ, pendconn);
        connection_free(pendconn);
      }
    }
    resolve->pending_connections = pend->next;
    tor_free(pend);
Loading