[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[or-cvs] Implement controller"s "extendcircuit" directive.



Update of /home2/or/cvsroot/tor/src/or
In directory moria.mit.edu:/home2/arma/work/onion/cvs/tor/src/or

Modified Files:
	circuitbuild.c circuitlist.c circuituse.c control.c or.h 
	rendclient.c router.c routerlist.c 
Log Message:
Implement controller's "extendcircuit" directive.
Also refactor circuit building so we plan the whole path ahead
of time.


Index: circuitbuild.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/circuitbuild.c,v
retrieving revision 1.89
retrieving revision 1.90
diff -u -d -r1.89 -r1.90
--- circuitbuild.c	19 Mar 2005 23:58:42 -0000	1.89
+++ circuitbuild.c	22 Mar 2005 00:42:38 -0000	1.90
@@ -19,14 +19,13 @@
 
 /********* END VARIABLES ************/
 
-static int
-circuit_deliver_create_cell(circuit_t *circ, char *payload);
-static cpath_build_state_t *
-onion_new_cpath_build_state(uint8_t purpose, const char *exit_digest,
-                            int need_uptime, int need_capacity, int internal);
-static int onion_extend_cpath(crypt_path_t **head_ptr,
-                       cpath_build_state_t *state, routerinfo_t **router_out);
+static int circuit_deliver_create_cell(circuit_t *circ, char *payload);
+static int onion_pick_cpath_exit(circuit_t *circ, routerinfo_t *exit);
+static crypt_path_t *onion_next_hop_in_cpath(crypt_path_t *cpath);
+static int onion_next_router_in_cpath(circuit_t *circ, routerinfo_t **router);
+static int onion_extend_cpath(crypt_path_t **head_ptr, cpath_build_state_t *state);
 static int count_acceptable_routers(smartlist_t *routers);
+static int onion_append_hop(crypt_path_t **head_ptr, routerinfo_t *choice);
 
 /** Iterate over values of circ_id, starting from conn-\>next_circ_id,
  * and with the high bit specified by circ_id_type (see
@@ -229,7 +228,39 @@
   }
 }
 
-/** Build a new circuit for <b>purpose</b>. If <b>exit_digest</b>
+/** Pick all the entries in our cpath. Stop and return 0 when we're
+ * happy, or return -1 if an error occurs. */
+static int
+onion_populate_cpath(circuit_t *circ) {
+  int r;
+again:
+  r = onion_extend_cpath(&circ->cpath, circ->build_state);
+//    || !CIRCUIT_IS_ORIGIN(circ)) { // wtf? -rd
+  if (r < 0) {
+    log_fn(LOG_INFO,"Generating cpath hop failed.");
+    circuit_mark_for_close(circ);
+    return -1;
+  }
+  if (r == 0)
+    goto again;
+  return 0; /* if r == 1 */
+}
+
+/** Create and return new circuit. Initialize its purpose and
+ * build-state based on our arguments. */
+circuit_t *
+circuit_init(uint8_t purpose, int need_uptime, int need_capacity, int internal) {
+  circuit_t *circ = circuit_new(0, NULL); /* sets circ->p_circ_id and circ->p_conn */
+  circ->state = CIRCUIT_STATE_OR_WAIT;
+  circ->build_state = tor_malloc_zero(sizeof(cpath_build_state_t));
+  circ->build_state->need_uptime = need_uptime;
+  circ->build_state->need_capacity = need_capacity;
+  circ->build_state->is_internal = internal;
+  circ->purpose = purpose;
+  return circ;
+}
+
+/** Build a new circuit for <b>purpose</b>. If <b>exit</b>
  * is defined, then use that as your exit router, else choose a suitable
  * exit node.
  *
@@ -237,36 +268,39 @@
  * it's not open already.
  */
 circuit_t *
-circuit_establish_circuit(uint8_t purpose, const char *exit_digest,
+circuit_establish_circuit(uint8_t purpose, routerinfo_t *exit,
                           int need_uptime, int need_capacity, int internal) {
-  routerinfo_t *firsthop;
-  connection_t *n_conn;
   circuit_t *circ;
 
-  circ = circuit_new(0, NULL); /* sets circ->p_circ_id and circ->p_conn */
-  circ->state = CIRCUIT_STATE_OR_WAIT;
-  circ->build_state = onion_new_cpath_build_state(purpose, exit_digest,
-                                 need_uptime, need_capacity, internal);
-  circ->purpose = purpose;
+  circ = circuit_init(purpose, need_uptime, need_capacity, internal);
 
-  if (! circ->build_state) {
-    log_fn(LOG_INFO,"Generating cpath failed.");
+  if (onion_pick_cpath_exit(circ, exit) < 0 ||
+      onion_populate_cpath(circ) < 0) {
     circuit_mark_for_close(circ);
     return NULL;
   }
 
-  if (onion_extend_cpath(&circ->cpath, circ->build_state, &firsthop)<0 ||
-      !CIRCUIT_IS_ORIGIN(circ)) {
-    log_fn(LOG_INFO,"Generating first cpath hop failed.");
+  control_event_circuit_status(circ, CIRC_EVENT_LAUNCHED);
+
+  if (circuit_handle_first_hop(circ) < 0) {
     circuit_mark_for_close(circ);
     return NULL;
   }
+  return circ;
+}
 
-  control_event_circuit_status(circ, CIRC_EVENT_LAUNCHED);
-
-  /* now see if we're already connected to the first OR in 'route' */
+/** Start establishing the first hop of our circuit. Figure out what
+ * OR we should connect to, and if necessary start the connection to
+ * it. If we're already connected, then send the 'create' cell.
+ * Return 0 for ok, -1 if circ should be marked-for-close. */
+int circuit_handle_first_hop(circuit_t *circ) {
+  routerinfo_t *firsthop;
+  connection_t *n_conn;
 
+  onion_next_router_in_cpath(circ, &firsthop);
   tor_assert(firsthop);
+
+  /* now see if we're already connected to the first OR in 'route' */
   log_fn(LOG_DEBUG,"Looking for firsthop '%s:%u'",
       firsthop->address,firsthop->or_port);
   /* imprint the circuit with its future n_conn->id */
@@ -282,8 +316,7 @@
                                      firsthop->identity_digest);
       if (!n_conn) { /* connect failed, forget the whole thing */
         log_fn(LOG_INFO,"connect to firsthop failed. Closing.");
-        circuit_mark_for_close(circ);
-        return NULL;
+        return -1;
       }
     }
 
@@ -291,7 +324,7 @@
     /* return success. The onion/circuit/etc will be taken care of automatically
      * (may already have been) whenever n_conn reaches OR_CONN_STATE_OPEN.
      */
-    return circ;
+    return 0;
   } else { /* it's already open. use it. */
     circ->n_addr = n_conn->addr;
     circ->n_port = n_conn->port;
@@ -299,11 +332,10 @@
     log_fn(LOG_DEBUG,"Conn open. Delivering first onion skin.");
     if (circuit_send_next_onion_skin(circ) < 0) {
       log_fn(LOG_INFO,"circuit_send_next_onion_skin failed.");
-      circuit_mark_for_close(circ);
-      return NULL;
+      return -1;
     }
   }
-  return circ;
+  return 0;
 }
 
 /** Find circuits that are waiting on <b>or_conn</b> to become open,
@@ -425,8 +457,8 @@
     tor_assert(circ->cpath->state == CPATH_STATE_OPEN);
     tor_assert(circ->state == CIRCUIT_STATE_BUILDING);
     log_fn(LOG_DEBUG,"starting to send subsequent skin.");
-    r = onion_extend_cpath(&circ->cpath, circ->build_state, &router);
-    if (r==1) {
+    r = onion_next_router_in_cpath(circ, &router);
+    if (r > 0) {
       /* done building the circuit. whew. */
       circ->state = CIRCUIT_STATE_OPEN;
       log_fn(LOG_INFO,"circuit built!");
@@ -439,11 +471,10 @@
       circuit_rep_hist_note_result(circ);
       circuit_has_opened(circ); /* do other actions as necessary */
       return 0;
-    } else if (r<0) {
-      log_fn(LOG_INFO,"Unable to extend circuit path.");
+    } else if (r < 0) {
       return -1;
     }
-    hop = circ->cpath->prev;
+    hop = onion_next_hop_in_cpath(circ->cpath);
 
     *(uint32_t*)payload = htonl(hop->addr);
     *(uint16_t*)(payload+4) = htons(hop->port);
@@ -469,6 +500,9 @@
   return 0;
 }
 
+/** Our clock just jumped forward by <b>seconds_elapsed</b>. Assume
+ * something has also gone wrong with our network: notify the user,
+ * and abandon all not-yet-used circuits. */
 void circuit_note_clock_jumped(int seconds_elapsed) {
   log_fn(LOG_NOTICE,"Your clock just jumped %d seconds forward; assuming established circuits no longer work.", seconds_elapsed);
   has_completed_circuit=0; /* so it'll log when it works again */
@@ -620,10 +654,8 @@
   if (circ->cpath->state == CPATH_STATE_AWAITING_KEYS)
     hop = circ->cpath;
   else {
-    for (hop=circ->cpath->next;
-        hop != circ->cpath && hop->state == CPATH_STATE_OPEN;
-        hop=hop->next) ;
-    if (hop == circ->cpath) { /* got an extended when we're all done? */
+    hop = onion_next_hop_in_cpath(circ->cpath);
+    if (!hop) { /* got an extended when we're all done? */
       log_fn(LOG_WARN,"got extended when circ already built? Closing.");
       return -1;
     }
@@ -646,7 +678,7 @@
   }
 
   hop->state = CPATH_STATE_OPEN;
-  log_fn(LOG_INFO,"Finished building circuit:");
+  log_fn(LOG_INFO,"Finished building circuit hop:");
   circuit_log_path(LOG_INFO,circ);
   control_event_circuit_status(circ, CIRC_EVENT_EXTENDED);
 
@@ -1101,81 +1133,53 @@
   return NULL;
 }
 
-/** Allocate a cpath_build_state_t, populate it based on
- * <b>purpose</b> and <b>exit_digest</b> (if specified), and
- * return it.
- */
-static cpath_build_state_t *
-onion_new_cpath_build_state(uint8_t purpose, const char *exit_digest,
-                            int need_uptime, int need_capacity, int internal)
-{
+/** Decide a suitable length for circ's cpath, and pick an exit
+ * router (or use <b>exit</b> if provided). Store these in the
+ * cpath. Return 0 if ok, -1 if circuit should be closed. */
+static int
+onion_pick_cpath_exit(circuit_t *circ, routerinfo_t *exit) {
+  cpath_build_state_t *state = circ->build_state;
+
   routerlist_t *rl;
   int r;
-  cpath_build_state_t *info;
-  routerinfo_t *exit;
 
   router_get_routerlist(&rl);
-  if (!rl)
-    return NULL;
-  r = new_route_len(get_options()->PathlenCoinWeight, purpose, rl->routers);
+  if (!rl) {
+    log_fn(LOG_WARN,"router_get_routerlist returned empty list; closing circ.");
+    return -1;
+  }
+  r = new_route_len(get_options()->PathlenCoinWeight, circ->purpose, rl->routers);
   if (r < 1) /* must be at least 1 */
-    return NULL;
-  info = tor_malloc_zero(sizeof(cpath_build_state_t));
-  info->desired_path_len = r;
-  info->need_uptime = need_uptime;
-  info->need_capacity = need_capacity;
-  info->is_internal = internal;
-  if (exit_digest) { /* the circuit-builder pre-requested one */
-    memcpy(info->chosen_exit_digest, exit_digest, DIGEST_LEN);
-    exit = router_get_by_digest(exit_digest);
-    if (exit) {
-      info->chosen_exit_name = tor_strdup(exit->nickname);
-    } else {
-      info->chosen_exit_name = tor_malloc(HEX_DIGEST_LEN+1);
-      base16_encode(info->chosen_exit_name, HEX_DIGEST_LEN+1,
-                    exit_digest, DIGEST_LEN);
-    }
-    log_fn(LOG_INFO,"Using requested exit node '%s'", info->chosen_exit_name);
+    return -1;
+  state->desired_path_len = r;
+
+  if (exit) { /* the circuit-builder pre-requested one */
+    log_fn(LOG_INFO,"Using requested exit node '%s'", exit->nickname);
   } else { /* we have to decide one */
-    exit = choose_good_exit_server(purpose, rl, need_uptime, need_capacity);
+    exit = choose_good_exit_server(circ->purpose, rl,
+                                   state->need_uptime, state->need_capacity);
     if (!exit) {
       log_fn(LOG_WARN,"failed to choose an exit server");
-      tor_free(info);
-      return NULL;
+      return -1;
     }
-    memcpy(info->chosen_exit_digest, exit->identity_digest, DIGEST_LEN);
-    info->chosen_exit_name = tor_strdup(exit->nickname);
   }
-  return info;
+  memcpy(state->chosen_exit_digest, exit->identity_digest, DIGEST_LEN);
+  state->chosen_exit_name = tor_strdup(exit->nickname);
+  return 0;
 }
 
 /** Take the open circ originating here, give it a new exit destination
- * to exit_digest (use nickname directly if it's provided, else strdup
- * out of router->nickname), and get it to send the next extend cell.
+ * to <b>exit</b>, and get it to send the next extend cell.
  */
 int
-circuit_append_new_hop(circuit_t *circ, char *nickname, const char *exit_digest) {
-  routerinfo_t *exit = router_get_by_digest(exit_digest);
-  tor_assert(CIRCUIT_IS_ORIGIN(circ));
-  circ->state = CIRCUIT_STATE_BUILDING;
+circuit_append_new_exit(circuit_t *circ, routerinfo_t *exit) {
+  tor_assert(exit);
+  tor_assert(circ && CIRCUIT_IS_ORIGIN(circ));
   tor_free(circ->build_state->chosen_exit_name);
-  if (nickname) {
-    circ->build_state->chosen_exit_name = nickname;
-  } else if (exit) {
-    circ->build_state->chosen_exit_name = tor_strdup(exit->nickname);
-  } else {
-    circ->build_state->chosen_exit_name = tor_malloc(HEX_DIGEST_LEN+1);
-    base16_encode(circ->build_state->chosen_exit_name, HEX_DIGEST_LEN+1,
-                  exit_digest, DIGEST_LEN);
-  }
-  memcpy(circ->build_state->chosen_exit_digest, exit_digest, DIGEST_LEN);
+  circ->build_state->chosen_exit_name = tor_strdup(exit->nickname);
+  memcpy(circ->build_state->chosen_exit_digest, exit->identity_digest, DIGEST_LEN);
   ++circ->build_state->desired_path_len;
-  if (circuit_send_next_onion_skin(circ)<0) {
-    log_fn(LOG_WARN, "Couldn't extend circuit to new point '%s'.",
-           circ->build_state->chosen_exit_name);
-    circuit_mark_for_close(circ);
-    return -1;
-  }
+  onion_append_hop(&circ->cpath, exit);
   return 0;
 }
 
@@ -1296,21 +1300,52 @@
   return choice;
 }
 
+/** Return the first non-open hop in cpath, or return NULL if all
+ * hops are open. */
+static crypt_path_t *
+onion_next_hop_in_cpath(crypt_path_t *cpath) {
+  crypt_path_t *hop = cpath;
+  do {
+    if (hop->state != CPATH_STATE_OPEN)
+      return hop;
+    hop = hop->next;
+  } while (hop != cpath);
+  return NULL;
+}
+
+/** Find the router corresponding to the first non-open hop in
+ * circ->cpath. Make sure it's state closed. Return 1 if all
+ * hops are open (the circuit is complete), 0 if we find a router
+ * (and set it to *router), and -1 if we fail to lookup the router.
+ */
+static int
+onion_next_router_in_cpath(circuit_t *circ, routerinfo_t **router) {
+  routerinfo_t *r;
+  crypt_path_t *hop = onion_next_hop_in_cpath(circ->cpath);
+  if (!hop) /* all hops are open */
+    return 1;
+  tor_assert(hop->state == CPATH_STATE_CLOSED);
+  r = router_get_by_digest(hop->identity_digest);
+  if (!r) {
+    log_fn(LOG_WARN,"Circuit intended to extend to a hop whose routerinfo we've lost. Cancelling circuit.");
+    return -1;
+  }
+  *router = r;
+  return 0;
+}
+
 /** Choose a suitable next hop in the cpath <b>head_ptr</b>,
- * based on <b>state</b>. Add the hop info to head_ptr, and return a
- * pointer to the chosen router in <b>router_out</b>.
+ * based on <b>state</b>. Append the hop info to head_ptr.
  */
 static int
-onion_extend_cpath(crypt_path_t **head_ptr, cpath_build_state_t
-                   *state, routerinfo_t **router_out)
+onion_extend_cpath(crypt_path_t **head_ptr, cpath_build_state_t *state)
 {
   int cur_len;
-  crypt_path_t *cpath, *hop;
+  crypt_path_t *cpath;
   routerinfo_t *choice;
   smartlist_t *excludednodes;
 
   tor_assert(head_ptr);
-  tor_assert(router_out);
 
   if (!*head_ptr) {
     cur_len = 0;
@@ -1320,11 +1355,13 @@
       ++cur_len;
     }
   }
+
   if (cur_len >= state->desired_path_len) {
     log_fn(LOG_DEBUG, "Path is complete: %d steps long",
            state->desired_path_len);
     return 1;
   }
+
   log_fn(LOG_DEBUG, "Path is %d long; we want %d", cur_len,
          state->desired_path_len);
 
@@ -1348,7 +1385,16 @@
   log_fn(LOG_DEBUG,"Chose router %s for hop %d (exit is %s)",
          choice->nickname, cur_len+1, state->chosen_exit_name);
 
-  hop = tor_malloc_zero(sizeof(crypt_path_t));
+  onion_append_hop(head_ptr, choice);
+  return 0;
+}
+
+/** Create a new hop, annotate it with information about its
+ * corresponding router <b>choice</b>, and append it to the
+ * end of the cpath <b>head_ptr</b>. */
+static int
+onion_append_hop(crypt_path_t **head_ptr, routerinfo_t *choice) {
+  crypt_path_t *hop = tor_malloc_zero(sizeof(crypt_path_t));
 
   /* link hop into the cpath, at the end. */
   onion_append_to_cpath(head_ptr, hop);
@@ -1362,9 +1408,6 @@
   hop->package_window = CIRCWINDOW_START;
   hop->deliver_window = CIRCWINDOW_START;
 
-  log_fn(LOG_DEBUG, "Extended circuit path with %s for hop %d",
-         choice->nickname, cur_len+1);
-
-  *router_out = choice;
   return 0;
 }
+

Index: circuitlist.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/circuitlist.c,v
retrieving revision 1.30
retrieving revision 1.31
diff -u -d -r1.30 -r1.31
--- circuitlist.c	19 Mar 2005 23:58:42 -0000	1.30
+++ circuitlist.c	22 Mar 2005 00:42:38 -0000	1.31
@@ -76,7 +76,8 @@
  */
 circuit_t *circuit_new(uint16_t p_circ_id, connection_t *p_conn) {
   circuit_t *circ;
-  static uint32_t n_circuits_allocated = 0;
+  static uint32_t n_circuits_allocated = 1;
+    /* never zero, since a global ID of 0 treated specially by the controller */
 
   circ = tor_malloc_zero(sizeof(circuit_t));
   circ->magic = CIRCUIT_MAGIC;

Index: circuituse.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/circuituse.c,v
retrieving revision 1.57
retrieving revision 1.58
diff -u -d -r1.57 -r1.58
--- circuituse.c	19 Mar 2005 06:57:15 -0000	1.57
+++ circuituse.c	22 Mar 2005 00:42:38 -0000	1.58
@@ -356,14 +356,14 @@
     if (need_ports) {
       log_fn(LOG_INFO,"Have %d clean circs (%d internal), need another exit circ.",
         num, num_internal);
-      circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL,
+      circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL,
                                port_needs_uptime, port_needs_capacity, 0);
     } else if (need_hidserv &&
                ((num_uptime_internal<2 && hidserv_needs_uptime) ||
                 num_internal<2)) {
       log_fn(LOG_INFO,"Have %d clean circs (%d uptime-internal, %d internal),"
         " need another hidserv circ.", num, num_uptime_internal, num_internal);
-      circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL,
+      circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL,
                                hidserv_needs_uptime, hidserv_needs_capacity, 1);
     }
   }
@@ -400,7 +400,7 @@
         circ &&
         circ->timestamp_created + TESTING_CIRCUIT_INTERVAL < now) {
       log_fn(LOG_INFO,"Creating a new testing circuit.");
-      circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL, 0, 0, 0);
+      circuit_launch_by_router(CIRCUIT_PURPOSE_C_GENERAL, NULL, 0, 0, 0);
     }
 #endif
   }
@@ -562,7 +562,7 @@
   routerinfo_t *me = router_get_my_routerinfo();
 
   if (!at_last_hop)
-    circuit_launch_by_identity(CIRCUIT_PURPOSE_TESTING, me->identity_digest, 0, 0, 1);
+    circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, 0, 0, 1);
   else
     log_fn(LOG_INFO,"Our testing circuit (to see if your ORPort is reachable) has failed. I'll try again later.");
 }
@@ -684,9 +684,10 @@
  * success. */
 #define MAX_CIRCUIT_FAILURES 5
 
+/** Launch a new circuit based on our arguments. */
 circuit_t *
-circuit_launch_by_identity(uint8_t purpose, const char *exit_digest,
-                           int need_uptime, int need_capacity, int internal)
+circuit_launch_by_router(uint8_t purpose, routerinfo_t *exit,
+                         int need_uptime, int need_capacity, int internal)
 {
   circuit_t *circ;
 
@@ -720,9 +721,15 @@
         case CIRCUIT_PURPOSE_C_INTRODUCING:
         case CIRCUIT_PURPOSE_S_CONNECT_REND:
           /* need to add a new hop */
-          tor_assert(exit_digest);
-          if (circuit_append_new_hop(circ, NULL, exit_digest) < 0)
+          tor_assert(exit);
+          circuit_append_new_exit(circ, exit);
+          circ->state = CIRCUIT_STATE_BUILDING;
+          if (circuit_send_next_onion_skin(circ)<0) {
+            log_fn(LOG_WARN, "Couldn't extend circuit to new point '%s'.",
+                   circ->build_state->chosen_exit_name);
+            circuit_mark_for_close(circ);
             return NULL;
+          }
           break;
         default:
           log_fn(LOG_WARN,"Bug: unexpected purpose %d when cannibalizing a general circ.",
@@ -744,7 +751,7 @@
   }
 
   /* try a circ. if it fails, circuit_mark_for_close will increment n_circuit_failures */
-  return circuit_establish_circuit(purpose, exit_digest,
+  return circuit_establish_circuit(purpose, exit,
                                    need_uptime, need_capacity, internal);
 }
 
@@ -753,18 +760,17 @@
 circuit_launch_by_nickname(uint8_t purpose, const char *exit_nickname,
                            int need_uptime, int need_capacity, int internal)
 {
-  const char *digest = NULL;
+  routerinfo_t *router = NULL;
 
   if (exit_nickname) {
-    routerinfo_t *r = router_get_by_nickname(exit_nickname);
-    if (!r) {
+    router = router_get_by_nickname(exit_nickname);
+    if (!router) {
       log_fn(LOG_WARN, "No such OR as '%s'", exit_nickname);
       return NULL;
     }
-    digest = r->identity_digest;
   }
-  return circuit_launch_by_identity(purpose, digest,
-                                    need_uptime, need_capacity, internal);
+  return circuit_launch_by_router(purpose, router,
+                                  need_uptime, need_capacity, internal);
 }
 
 /** Record another failure at opening a general circuit. When we have

Index: control.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/control.c,v
retrieving revision 1.58
retrieving revision 1.59
diff -u -d -r1.58 -r1.59
--- control.c	19 Mar 2005 06:05:55 -0000	1.58
+++ control.c	22 Mar 2005 00:42:38 -0000	1.59
@@ -642,12 +642,6 @@
     send_control_error(conn, ERR_SYNTAX, "extendcircuit message too short");
     return 0;
   }
-  circ_id = ntohl(get_uint32(body));
-  if (!(circ = circuit_get_by_global_id(circ_id))) {
-    send_control_error(conn, ERR_NO_STREAM,
-                       "No connection found with given ID");
-    return 0;
-  }
 
   router_nicknames = smartlist_create();
   routers = smartlist_create();
@@ -661,20 +655,45 @@
       }
       smartlist_add(routers, r);
     });
+  if (!smartlist_len(routers)) {
+    send_control_error(conn, ERR_SYNTAX, "No router names provided");
+    goto done;
+  }
+
+  circ_id = ntohl(get_uint32(body));
+  if (!circ_id) {
+    /* start a new circuit */
+    circ = circuit_init(CIRCUIT_PURPOSE_C_GENERAL, 0, 0, 0);
+  } else {
+    circ = circuit_get_by_global_id(circ_id);
+    if (!circ) {
+      send_control_error(conn, ERR_NO_CIRC,
+                         "No circuit found with given ID");
+      goto done;
+    }
+  }
+
+  /* now circ refers to something that is ready to be extended */
 
-#if 1
-  /*XXXX RD*/
-  send_control_error(conn, ERR_INTERNAL, "EXTENDCIRCUIT not implemented.");
-#else
   SMARTLIST_FOREACH(routers, routerinfo_t *, r,
     {
-      /*XXXX RD*/
-      if (circuit_extend_path(circ, r)<0) {
-        send_control_error(conn, ERR_INTERNAL, "Unable to extend path.");
-        goto done;
-      }
+      circuit_append_new_exit(circ, r);
     });
-#endif
+
+  /* now that we've populated the cpath, start extending */
+  if (!circ_id) {
+    if (circuit_handle_first_hop(circ) < 0) {
+      circuit_mark_for_close(circ);
+    }
+  } else {
+    if (circ->state == CIRCUIT_STATE_OPEN) {
+      circ->state = CIRCUIT_STATE_BUILDING;
+      if (circuit_send_next_onion_skin(circ) < 0) {
+        log_fn(LOG_INFO,"send_next_onion_skin failed; circuit marked for closing.");
+        circuit_mark_for_close(circ);
+      }
+    }
+  }
 
   send_control_done(conn);
  done:

Index: or.h
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/or.h,v
retrieving revision 1.562
retrieving revision 1.563
diff -u -d -r1.562 -r1.563
--- or.h	19 Mar 2005 23:58:42 -0000	1.562
+++ or.h	22 Mar 2005 00:42:38 -0000	1.563
@@ -1120,8 +1120,11 @@
 void circuit_log_path(int severity, circuit_t *circ);
 void circuit_rep_hist_note_result(circuit_t *circ);
 void circuit_dump_by_conn(connection_t *conn, int severity);
-circuit_t *circuit_establish_circuit(uint8_t purpose, const char *exit_digest,
+circuit_t *circuit_init(uint8_t purpose, int need_uptime,
+                        int need_capacity, int internal);
+circuit_t *circuit_establish_circuit(uint8_t purpose, routerinfo_t *exit,
                                      int need_uptime, int need_capacity, int internal);
+int circuit_handle_first_hop(circuit_t *circ);
 void circuit_n_conn_done(connection_t *or_conn, int status);
 int circuit_send_next_onion_skin(circuit_t *circ);
 void circuit_note_clock_jumped(int seconds_elapsed);
@@ -1133,7 +1136,7 @@
 int circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
                                         int *need_capacity);
 
-int circuit_append_new_hop(circuit_t *circ, char *nickname, const char *exit_digest);
+int circuit_append_new_exit(circuit_t *circ, routerinfo_t *exit);
 void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
 
 /********************************* circuitlist.c ***********************/
@@ -1181,8 +1184,8 @@
 void circuit_build_failed(circuit_t *circ);
 circuit_t *circuit_launch_by_nickname(uint8_t purpose, const char *exit_nickname,
                                       int need_uptime, int need_capacity, int is_internal);
-circuit_t *circuit_launch_by_identity(uint8_t purpose, const char *exit_digest,
-                                      int need_uptime, int need_capacity, int is_internal);
+circuit_t *circuit_launch_by_router(uint8_t purpose, routerinfo_t *exit,
+                                    int need_uptime, int need_capacity, int is_internal);
 void circuit_reset_failure_count(int timeout);
 int connection_ap_handshake_attach_chosen_circuit(connection_t *conn,
                                                   circuit_t *circ);

Index: rendclient.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/rendclient.c,v
retrieving revision 1.76
retrieving revision 1.77
diff -u -d -r1.76 -r1.77
--- rendclient.c	19 Jan 2005 23:15:59 -0000	1.76
+++ rendclient.c	22 Mar 2005 00:42:38 -0000	1.77
@@ -225,8 +225,15 @@
       }
       log_fn(LOG_INFO, "Chose new intro point %s for %s (circ %d)",
              nickname, circ->rend_query, circ->n_circ_id);
-      if (circuit_append_new_hop(circ, nickname, r->identity_digest) < 0)
+      tor_free(nickname);
+      circuit_append_new_exit(circ, r);
+      circ->state = CIRCUIT_STATE_BUILDING;
+      if (circuit_send_next_onion_skin(circ)<0) {
+        log_fn(LOG_WARN, "Couldn't extend circuit to new point '%s'.",
+               circ->build_state->chosen_exit_name);
+        circuit_mark_for_close(circ);
         return -1;
+      }
     }
   }
   return 0;

Index: router.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/router.c,v
retrieving revision 1.155
retrieving revision 1.156
diff -u -d -r1.155 -r1.156
--- router.c	19 Mar 2005 23:04:15 -0000	1.155
+++ router.c	22 Mar 2005 00:42:38 -0000	1.156
@@ -385,7 +385,7 @@
   routerinfo_t *me = router_get_my_routerinfo();
 
   if (!can_reach_or_port) {
-    circuit_launch_by_identity(CIRCUIT_PURPOSE_TESTING, me->identity_digest, 0, 0, 1);
+    circuit_launch_by_router(CIRCUIT_PURPOSE_TESTING, me, 0, 0, 1);
   }
 
   if (!can_reach_dir_port && me->dir_port) {

Index: routerlist.c
===================================================================
RCS file: /home2/or/cvsroot/tor/src/or/routerlist.c,v
retrieving revision 1.220
retrieving revision 1.221
diff -u -d -r1.220 -r1.221
--- routerlist.c	19 Mar 2005 06:57:16 -0000	1.220
+++ routerlist.c	22 Mar 2005 00:42:38 -0000	1.221
@@ -628,7 +628,7 @@
 }
 
 /** Return the router in our routerlist whose 20-byte key digest
- * is <b>hexdigest</b>.  Return NULL if no such router is known. */
+ * is <b>digest</b>.  Return NULL if no such router is known. */
 routerinfo_t *router_get_by_digest(const char *digest) {
   int i;
   routerinfo_t *router;