Browse Source

r9289@31-35-219: nickm | 2006-10-20 09:43:22 -0400
Fix longstanding bug in connection_exit_begin_conn(): Since connection_edge_end() exits when the connection is unattached, we were never sending RELAY_END cells back for failed RELAY_BEGIN attempts. Fix this. This might make clients that were otherwise timing out either fail faster or retry faster, which is good news for us.


svn:r8770

Nick Mathewson 17 years ago
parent
commit
136ed33071
5 changed files with 107 additions and 52 deletions
  1. 4 0
      ChangeLog
  2. 2 2
      doc/TODO
  3. 45 20
      src/or/connection_edge.c
  4. 3 0
      src/or/or.h
  5. 53 30
      src/or/relay.c

+ 4 - 0
ChangeLog

@@ -48,6 +48,10 @@ Changes in version 0.1.2.3-alpha - 2006-10-??
     - Detect the size of the routers file correctly even if it is corrupted
     - Detect the size of the routers file correctly even if it is corrupted
       (on systems without mmap) or not page-aligned (on systems with mmap).
       (on systems without mmap) or not page-aligned (on systems with mmap).
       This bug was harmless.
       This bug was harmless.
+    - Implement the protocol correctly by always sending a RELAY_END cell
+      when an attempt to open a stream fails.  This should make clients
+      able to find a good exit faster in some cases, since unhandleable
+      requests will now get an error rather than timing out.
 
 
 
 
 Changes in version 0.1.2.2-alpha - 2006-10-07
 Changes in version 0.1.2.2-alpha - 2006-10-07

+ 2 - 2
doc/TODO

@@ -57,9 +57,9 @@ N . Have (and document) a BEGIN_DIR relay cell that means "Connect to your
     - Use for something, so we can be sure it works.
     - Use for something, so we can be sure it works.
     - Test and debug
     - Test and debug
 
 
-N - Send back RELAY_END cells on malformed RELAY_BEGIN.
+  o Send back RELAY_END cells on malformed RELAY_BEGIN.
 
 
-N - Change the circuit end reason display a little for reasons from
+  o Change the circuit end reason display a little for reasons from
     destroyed/truncated circuits.  We want to indicate both that we're
     destroyed/truncated circuits.  We want to indicate both that we're
     closing because somebody told us to, and why they told us they wanted to
     closing because somebody told us to, and why they told us they wanted to
     close.
     close.

+ 45 - 20
src/or/connection_edge.c

@@ -1838,36 +1838,51 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   relay_header_t rh;
   relay_header_t rh;
   char *address=NULL;
   char *address=NULL;
   uint16_t port;
   uint16_t port;
+  char end_payload[1];
 
 
   assert_circuit_ok(circ);
   assert_circuit_ok(circ);
 
 
-  /* XXX currently we don't send an end cell back if we drop the
-   * begin because it's malformed.
-   */
+  relay_header_unpack(&rh, cell->payload);
+
+  /* Note: we have to use relay_send_command_from_edge here, not
+   * connection_edge_end or connection_edge_send_command, since those require
+   * that we have a stream connected to a circuit, and we don't connect to a
+   * circuit unitl we have a pending/sucessful resolve. */
 
 
   if (!server_mode(get_options()) &&
   if (!server_mode(get_options()) &&
       circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED) {
       circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED) {
     log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
     log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
            "Relay begin cell at non-server. Dropping.");
            "Relay begin cell at non-server. Dropping.");
+    end_payload[0] = END_STREAM_REASON_EXITPOLICY;
+    relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                 end_payload, 1, NULL);
     return 0;
     return 0;
   }
   }
 
 
-  relay_header_unpack(&rh, cell->payload);
   if (rh.command == RELAY_COMMAND_BEGIN) {
   if (rh.command == RELAY_COMMAND_BEGIN) {
     if (!memchr(cell->payload+RELAY_HEADER_SIZE, 0, rh.length)) {
     if (!memchr(cell->payload+RELAY_HEADER_SIZE, 0, rh.length)) {
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Relay begin cell has no \\0. Dropping.");
              "Relay begin cell has no \\0. Dropping.");
+      end_payload[0] = END_STREAM_REASON_TORPROTOCOL;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       return 0;
       return 0;
     }
     }
     if (parse_addr_port(LOG_PROTOCOL_WARN, cell->payload+RELAY_HEADER_SIZE,
     if (parse_addr_port(LOG_PROTOCOL_WARN, cell->payload+RELAY_HEADER_SIZE,
                         &address,NULL,&port)<0) {
                         &address,NULL,&port)<0) {
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Unable to parse addr:port in relay begin cell. Dropping.");
              "Unable to parse addr:port in relay begin cell. Dropping.");
+      end_payload[0] = END_STREAM_REASON_TORPROTOCOL;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       return 0;
       return 0;
     }
     }
     if (port==0) {
     if (port==0) {
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Missing port in relay begin cell. Dropping.");
              "Missing port in relay begin cell. Dropping.");
+      end_payload[0] = END_STREAM_REASON_TORPROTOCOL;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       tor_free(address);
       tor_free(address);
       return 0;
       return 0;
     }
     }
@@ -1876,6 +1891,9 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Non-printing characters in address %s in relay "
              "Non-printing characters in address %s in relay "
              "begin cell. Dropping.", escaped(address));
              "begin cell. Dropping.", escaped(address));
+      end_payload[0] = END_STREAM_REASON_TORPROTOCOL;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       tor_free(address);
       tor_free(address);
       return 0;
       return 0;
     }
     }
@@ -1886,15 +1904,27 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
        */
        */
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Attempt to open a stream on first hop of circuit. Dropping.");
              "Attempt to open a stream on first hop of circuit. Dropping.");
+      end_payload[0] = END_STREAM_REASON_TORPROTOCOL;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       tor_free(address);
       tor_free(address);
       return 0;
       return 0;
     }
     }
   } else if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
   } else if (rh.command == RELAY_COMMAND_BEGIN_DIR) {
     or_options_t *options = get_options();
     or_options_t *options = get_options();
+    port = options->DirPort; /* not actually used to open a connection */
+    if (!port || circ->purpose != CIRCUIT_PURPOSE_OR) {
+      end_payload[0] = END_STREAM_REASON_NOTDIRECTORY;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
+      return 0;
+    }
     address = tor_strdup("127.0.0.1");
     address = tor_strdup("127.0.0.1");
-    port = options->DirPort; /* not actually used. */
   } else {
   } else {
     log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command);
     log_warn(LD_BUG, "Got an unexpected command %d", (int)rh.command);
+    end_payload[0] = END_STREAM_REASON_INTERNAL;
+    relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                 end_payload, 1, NULL);
     return 0;
     return 0;
   }
   }
 
 
@@ -1908,15 +1938,6 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   n_stream->package_window = STREAMWINDOW_START;
   n_stream->package_window = STREAMWINDOW_START;
   n_stream->deliver_window = STREAMWINDOW_START;
   n_stream->deliver_window = STREAMWINDOW_START;
 
 
-  if (rh.command == RELAY_COMMAND_BEGIN_DIR &&
-      (!get_options()->DirPort || circ->purpose != CIRCUIT_PURPOSE_OR)) {
-    connection_edge_end(n_stream, END_STREAM_REASON_NOTDIRECTORY,
-                        n_stream->cpath_layer);
-    connection_free(TO_CONN(n_stream));
-    tor_free(address);
-    return 0;
-  }
-
   if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
   if (circ->purpose == CIRCUIT_PURPOSE_S_REND_JOINED) {
     origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
     origin_circuit_t *origin_circ = TO_ORIGIN_CIRCUIT(circ);
     log_debug(LD_REND,"begin is for rendezvous. configuring stream.");
     log_debug(LD_REND,"begin is for rendezvous. configuring stream.");
@@ -1929,8 +1950,9 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
     if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) {
     if (rend_service_set_connection_addr_port(n_stream, origin_circ) < 0) {
       log_info(LD_REND,"Didn't find rendezvous service (port %d)",
       log_info(LD_REND,"Didn't find rendezvous service (port %d)",
                n_stream->_base.port);
                n_stream->_base.port);
-      connection_edge_end(n_stream, END_STREAM_REASON_EXITPOLICY,
-                          n_stream->cpath_layer);
+      end_payload[0] = END_STREAM_REASON_EXITPOLICY;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       connection_free(TO_CONN(n_stream));
       connection_free(TO_CONN(n_stream));
       /* knock the whole thing down, somebody screwed up */
       /* knock the whole thing down, somebody screwed up */
       circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED);
       circuit_mark_for_close(circ, END_CIRC_REASON_CONNECTFAILED);
@@ -1957,8 +1979,9 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
   /* default to failed, change in dns_resolve if it turns out not to fail */
   /* default to failed, change in dns_resolve if it turns out not to fail */
 
 
   if (we_are_hibernating()) {
   if (we_are_hibernating()) {
-    connection_edge_end(n_stream, END_STREAM_REASON_HIBERNATING,
-                        n_stream->cpath_layer);
+    end_payload[0] = END_STREAM_REASON_HIBERNATING;
+    relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                 end_payload, 1, NULL);
     connection_free(TO_CONN(n_stream));
     connection_free(TO_CONN(n_stream));
     return 0;
     return 0;
   }
   }
@@ -1985,7 +2008,9 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
       connection_exit_connect(n_stream);
       connection_exit_connect(n_stream);
       return 0;
       return 0;
     case -1: /* resolve failed */
     case -1: /* resolve failed */
-      /*  XXXX send back indication of failure for connect case? -NM*/
+      end_payload[0] = END_STREAM_REASON_RESOLVEFAILED;
+      relay_send_command_from_edge(rh.stream_id, circ, RELAY_COMMAND_END,
+                                   end_payload, 1, NULL);
       /* n_stream got freed. don't touch it. */
       /* n_stream got freed. don't touch it. */
       break;
       break;
     case 0: /* resolve added to pending list */
     case 0: /* resolve added to pending list */
@@ -2157,7 +2182,7 @@ connection_exit_connect_dir(edge_connection_t *exit_conn)
 
 
   if ((err = tor_socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) < 0) {
   if ((err = tor_socketpair(AF_UNIX, SOCK_STREAM, 0, fd)) < 0) {
     log_warn(LD_NET,
     log_warn(LD_NET,
-             "Couldn't construct socketpair (%s). Network down? Delaying.",
+             "Couldn't construct socketpair (%s). Out of sockets?",
              tor_socket_strerror(-err));
              tor_socket_strerror(-err));
     connection_edge_end(exit_conn, END_STREAM_REASON_RESOURCELIMIT,
     connection_edge_end(exit_conn, END_STREAM_REASON_RESOURCELIMIT,
                         exit_conn->cpath_layer);
                         exit_conn->cpath_layer);

+ 3 - 0
src/or/or.h

@@ -2332,6 +2332,9 @@ int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ,
 
 
 void relay_header_pack(char *dest, const relay_header_t *src);
 void relay_header_pack(char *dest, const relay_header_t *src);
 void relay_header_unpack(relay_header_t *dest, const char *src);
 void relay_header_unpack(relay_header_t *dest, const char *src);
+int relay_send_command_from_edge(uint16_t stream_id, circuit_t *circ,
+                                int relay_command, const char *payload,
+                                size_t payload_len, crypt_path_t *cpath_layer);
 int connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
 int connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
                                  int relay_command, const char *payload,
                                  int relay_command, const char *payload,
                                  size_t payload_len,
                                  size_t payload_len,

+ 53 - 30
src/or/relay.c

@@ -446,17 +446,17 @@ relay_header_unpack(relay_header_t *dest, const char *src)
   dest->length = ntohs(get_uint16(src+9));
   dest->length = ntohs(get_uint16(src+9));
 }
 }
 
 
-/** Make a relay cell out of <b>relay_command</b> and <b>payload</b>, and
- * send it onto the open circuit <b>circ</b>. <b>fromconn</b> is the stream
- * that's sending the relay cell, or NULL if it's a control cell.
- * <b>cpath_layer</b> is NULL for OR->OP cells, or the destination hop
- * for OP->OR cells.
+/** Make a relay cell out of <b>relay_command</b> and <b>payload</b>, and send
+ * it onto the open circuit <b>circ</b>. <b>stream_id</b> is the ID on
+ * <b>circ</b> for the stream that's sending the relay cell, or 0 if it's a
+ * control cell.  <b>cpath_layer</b> is NULL for OR->OP cells, or the
+ * destination hop for OP->OR cells.
  *
  *
- * If you can't send the cell, mark the circuit for close and
- * return -1. Else return 0.
+ * If you can't send the cell, mark the circuit for close and return -1. Else
+ * return 0.
  */
  */
 int
 int
-connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
+relay_send_command_from_edge(uint16_t stream_id, circuit_t *circ,
                              int relay_command, const char *payload,
                              int relay_command, const char *payload,
                              size_t payload_len, crypt_path_t *cpath_layer)
                              size_t payload_len, crypt_path_t *cpath_layer)
 {
 {
@@ -465,26 +465,7 @@ connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
   int cell_direction;
   int cell_direction;
   /* XXXX NM Split this function into a separate versions per circuit type? */
   /* XXXX NM Split this function into a separate versions per circuit type? */
 
 
-  if (fromconn && fromconn->_base.marked_for_close) {
-    log_warn(LD_BUG,
-             "Bug: called on conn that's already marked for close at %s:%d.",
-             fromconn->_base.marked_for_close_file,
-             fromconn->_base.marked_for_close);
-    return 0;
-  }
-
-  if (!circ) {
-    tor_assert(fromconn);
-    if (fromconn->_base.type == CONN_TYPE_AP) {
-      log_info(LD_APP,"no circ. Closing conn.");
-      connection_mark_unattached_ap(fromconn, END_STREAM_REASON_INTERNAL);
-    } else {
-      log_info(LD_EXIT,"no circ. Closing conn.");
-      fromconn->_base.edge_has_sent_end = 1; /* no circ to send to */
-      connection_mark_for_close(TO_CONN(fromconn));
-    }
-    return -1;
-  }
+  tor_assert(circ);
 
 
   memset(&cell, 0, sizeof(cell_t));
   memset(&cell, 0, sizeof(cell_t));
   cell.command = CELL_RELAY;
   cell.command = CELL_RELAY;
@@ -500,8 +481,7 @@ connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
 
 
   memset(&rh, 0, sizeof(rh));
   memset(&rh, 0, sizeof(rh));
   rh.command = relay_command;
   rh.command = relay_command;
-  if (fromconn)
-    rh.stream_id = fromconn->stream_id; /* else it's 0 */
+  rh.stream_id = stream_id;
   rh.length = payload_len;
   rh.length = payload_len;
   relay_header_pack(cell.payload, &rh);
   relay_header_pack(cell.payload, &rh);
   if (payload_len) {
   if (payload_len) {
@@ -521,6 +501,48 @@ connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
   return 0;
   return 0;
 }
 }
 
 
+/** Make a relay cell out of <b>relay_command</b> and <b>payload</b>, and
+ * send it onto the open circuit <b>circ</b>. <b>fromconn</b> is the stream
+ * that's sending the relay cell, or NULL if it's a control cell.
+ * <b>cpath_layer</b> is NULL for OR->OP cells, or the destination hop
+ * for OP->OR cells.
+ *
+ * If you can't send the cell, mark the circuit for close and
+ * return -1. Else return 0.
+ */
+int
+connection_edge_send_command(edge_connection_t *fromconn, circuit_t *circ,
+                             int relay_command, const char *payload,
+                             size_t payload_len, crypt_path_t *cpath_layer)
+{
+  /* XXXX NM Split this function into a separate versions per circuit type? */
+
+  if (fromconn && fromconn->_base.marked_for_close) {
+    log_warn(LD_BUG,
+             "Bug: called on conn that's already marked for close at %s:%d.",
+             fromconn->_base.marked_for_close_file,
+             fromconn->_base.marked_for_close);
+    return 0;
+  }
+
+  if (!circ) {
+    tor_assert(fromconn);
+    if (fromconn->_base.type == CONN_TYPE_AP) {
+      log_info(LD_APP,"no circ. Closing conn.");
+      connection_mark_unattached_ap(fromconn, END_STREAM_REASON_INTERNAL);
+    } else {
+      log_info(LD_EXIT,"no circ. Closing conn.");
+      fromconn->_base.edge_has_sent_end = 1; /* no circ to send to */
+      connection_mark_for_close(TO_CONN(fromconn));
+    }
+    return -1;
+  }
+
+  return relay_send_command_from_edge(fromconn ? fromconn->stream_id : 0,
+                                      circ, relay_command, payload,
+                                      payload_len, cpath_layer);
+}
+
 /** Translate <b>reason</b>, which came from a relay 'end' cell,
 /** Translate <b>reason</b>, which came from a relay 'end' cell,
  * into a static const string describing why the stream is closing.
  * into a static const string describing why the stream is closing.
  * <b>reason</b> is -1 if no reason was provided.
  * <b>reason</b> is -1 if no reason was provided.
@@ -545,6 +567,7 @@ connection_edge_end_reason_str(int reason)
     case END_STREAM_REASON_RESOURCELIMIT:  return "server out of resources";
     case END_STREAM_REASON_RESOURCELIMIT:  return "server out of resources";
     case END_STREAM_REASON_CONNRESET:      return "connection reset";
     case END_STREAM_REASON_CONNRESET:      return "connection reset";
     case END_STREAM_REASON_TORPROTOCOL:    return "Tor protocol error";
     case END_STREAM_REASON_TORPROTOCOL:    return "Tor protocol error";
+    case END_STREAM_REASON_NOTDIRECTORY:   return "not a directory";
     default:
     default:
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
       log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL,
              "Reason for ending (%d) not recognized.",reason);
              "Reason for ending (%d) not recognized.",reason);