summaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
Diffstat (limited to 'src/or')
-rw-r--r--src/or/circuitbuild.c5
-rw-r--r--src/or/circuituse.c11
-rw-r--r--src/or/command.c1
-rw-r--r--src/or/config.c38
-rw-r--r--src/or/connection.c3
-rw-r--r--src/or/control.c140
-rw-r--r--src/or/control.h4
-rw-r--r--src/or/directory.c2
-rw-r--r--src/or/dirvote.c3
-rw-r--r--src/or/eventdns.c5
-rw-r--r--src/or/main.c5
-rw-r--r--src/or/or.h15
-rw-r--r--src/or/rendclient.c11
13 files changed, 207 insertions, 36 deletions
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index 08bfb98815..9509b5ad18 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -262,7 +262,7 @@ circuit_build_times_test_frequency(void)
}
/**
- * Retrieve and bounds-check the cbtmintimeout consensus paramter.
+ * Retrieve and bounds-check the cbtmintimeout consensus parameter.
*
* Effect: This is the minimum allowed timeout value in milliseconds.
* The minimum is to prevent rounding to 0 (we only check once
@@ -3766,7 +3766,6 @@ void
entry_guards_compute_status(or_options_t *options, time_t now)
{
int changed = 0;
- int severity = LOG_DEBUG;
digestmap_t *reasons;
if (! entry_guards)
@@ -3793,8 +3792,6 @@ entry_guards_compute_status(or_options_t *options, time_t now)
if (remove_dead_entry_guards(now))
changed = 1;
- severity = changed ? LOG_DEBUG : LOG_INFO;
-
if (changed) {
SMARTLIST_FOREACH_BEGIN(entry_guards, entry_guard_t *, entry) {
const char *reason = digestmap_get(reasons, entry->identity);
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 7289aa5c11..97ac5600a2 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -288,7 +288,6 @@ circuit_expire_building(void)
struct timeval general_cutoff, begindir_cutoff, fourhop_cutoff,
cannibalize_cutoff, close_cutoff, extremely_old_cutoff;
struct timeval now;
- struct timeval introcirc_cutoff;
cpath_build_state_t *build_state;
tor_gettimeofday(&now);
@@ -307,8 +306,6 @@ circuit_expire_building(void)
SET_CUTOFF(close_cutoff, circ_times.close_ms);
SET_CUTOFF(extremely_old_cutoff, circ_times.close_ms*2 + 1000);
- introcirc_cutoff = begindir_cutoff;
-
while (next_circ) {
struct timeval cutoff;
victim = next_circ;
@@ -325,8 +322,6 @@ circuit_expire_building(void)
cutoff = fourhop_cutoff;
else if (TO_ORIGIN_CIRCUIT(victim)->has_opened)
cutoff = cannibalize_cutoff;
- else if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING)
- cutoff = introcirc_cutoff;
else if (victim->purpose == CIRCUIT_PURPOSE_C_MEASURE_TIMEOUT)
cutoff = close_cutoff;
else
@@ -337,12 +332,6 @@ circuit_expire_building(void)
#if 0
/* some debug logs, to help track bugs */
- if (victim->purpose == CIRCUIT_PURPOSE_C_INTRODUCING &&
- victim->timestamp_created <= introcirc_cutoff &&
- victim->timestamp_created > general_cutoff)
- log_info(LD_REND|LD_CIRC, "Timing out introduction circuit which we "
- "would not have done if it had been a general circuit.");
-
if (victim->purpose >= CIRCUIT_PURPOSE_C_INTRODUCING &&
victim->purpose <= CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED) {
if (!victim->timestamp_dirty)
diff --git a/src/or/command.c b/src/or/command.c
index 00d9af33fa..e377f4fb67 100644
--- a/src/or/command.c
+++ b/src/or/command.c
@@ -645,6 +645,7 @@ command_process_netinfo_cell(cell_t *cell, or_connection_t *conn)
/* XXX maybe act on my_apparent_addr, if the source is sufficiently
* trustworthy. */
+ (void)my_apparent_addr;
if (connection_or_set_state_open(conn)<0)
connection_mark_for_close(TO_CONN(conn));
diff --git a/src/or/config.c b/src/or/config.c
index 36fb991e33..117925549e 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -38,6 +38,8 @@
#include <shlobj.h>
#endif
+#include "procmon.h"
+
/** Enumeration of types which option values can take */
typedef enum config_type_t {
CONFIG_TYPE_STRING = 0, /**< An arbitrary string. */
@@ -398,6 +400,7 @@ static config_var_t _option_vars[] = {
VAR("__LeaveStreamsUnattached",BOOL, LeaveStreamsUnattached, "0"),
VAR("__HashedControlSessionPassword", LINELIST, HashedControlSessionPassword,
NULL),
+ VAR("__OwningControllerProcess",STRING,OwningControllerProcess, NULL),
V(MinUptimeHidServDirectoryV2, INTERVAL, "24 hours"),
V(_UsingTestNetworkDefaults, BOOL, "0"),
@@ -1169,12 +1172,26 @@ options_act(or_options_t *old_options)
or_options_t *options = get_options();
int running_tor = options->command == CMD_RUN_TOR;
char *msg;
+ const int transition_affects_workers =
+ old_options && options_transition_affects_workers(old_options, options);
if (running_tor && !have_lockfile()) {
if (try_locking(options, 1) < 0)
return -1;
}
+ /* We want to reinit keys as needed before we do much of anything else:
+ keys are important, and other things can depend on them. */
+ if (running_tor &&
+ (transition_affects_workers ||
+ (options->V3AuthoritativeDir && (!old_options ||
+ !old_options->V3AuthoritativeDir)))) {
+ if (init_keys() < 0) {
+ log_warn(LD_BUG,"Error initializing keys; exiting");
+ return -1;
+ }
+ }
+
if (consider_adding_dir_authorities(options, old_options) < 0)
return -1;
@@ -1241,6 +1258,8 @@ options_act(or_options_t *old_options)
return -1;
}
+ monitor_owning_controller_process(options->OwningControllerProcess);
+
/* reload keys as needed for rendezvous services. */
if (rend_service_load_keys()<0) {
log_warn(LD_GENERAL,"Error loading rendezvous service keys");
@@ -1341,14 +1360,10 @@ options_act(or_options_t *old_options)
}
}
- if (options_transition_affects_workers(old_options, options)) {
+ if (transition_affects_workers) {
log_info(LD_GENERAL,
"Worker-related options changed. Rotating workers.");
- if (init_keys() < 0) {
- log_warn(LD_BUG,"Error initializing keys; exiting");
- return -1;
- }
if (server_mode(options) && !server_mode(old_options)) {
ip_address_changed(0);
if (can_complete_circuit || !any_predicted_circuits(time(NULL)))
@@ -1362,9 +1377,6 @@ options_act(or_options_t *old_options)
return -1;
}
- if (options->V3AuthoritativeDir && !old_options->V3AuthoritativeDir)
- init_keys();
-
if (options->PerConnBWRate != old_options->PerConnBWRate ||
options->PerConnBWBurst != old_options->PerConnBWBurst)
connection_or_update_token_buckets(get_connection_array(), options);
@@ -3480,6 +3492,16 @@ options_validate(or_options_t *old_options, or_options_t *options,
}
}
+ if (options->OwningControllerProcess) {
+ const char *validate_pspec_msg = NULL;
+ if (tor_validate_process_specifier(options->OwningControllerProcess,
+ &validate_pspec_msg)) {
+ tor_asprintf(msg, "Bad OwningControllerProcess: %s",
+ validate_pspec_msg);
+ return -1;
+ }
+ }
+
if (options->ControlListenAddress) {
int all_are_local = 1;
config_line_t *ln;
diff --git a/src/or/connection.c b/src/or/connection.c
index e58aba3d98..6644b4cd76 100644
--- a/src/or/connection.c
+++ b/src/or/connection.c
@@ -479,8 +479,7 @@ connection_free(connection_t *conn)
}
}
if (conn->type == CONN_TYPE_CONTROL) {
- TO_CONTROL_CONN(conn)->event_mask = 0;
- control_update_global_event_mask();
+ connection_control_closed(TO_CONTROL_CONN(conn));
}
connection_unregister_events(conn);
_connection_free(conn);
diff --git a/src/or/control.c b/src/or/control.c
index a595b2a638..c7e22f81e2 100644
--- a/src/or/control.c
+++ b/src/or/control.c
@@ -32,6 +32,8 @@
#include "routerlist.h"
#include "routerparse.h"
+#include "procmon.h"
+
/** Yield true iff <b>s</b> is the state of a control_connection_t that has
* finished authentication and is accepting commands. */
#define STATE_IS_OPEN(s) ((s) == CONTROL_CONN_STATE_OPEN)
@@ -1275,6 +1277,26 @@ handle_control_signal(control_connection_t *conn, uint32_t len,
return 0;
}
+/** Called when we get a TAKEOWNERSHIP command. Mark this connection
+ * as an owning connection, so that we will exit if the connection
+ * closes. */
+static int
+handle_control_takeownership(control_connection_t *conn, uint32_t len,
+ const char *body)
+{
+ (void)len;
+ (void)body;
+
+ conn->is_owning_control_connection = 1;
+
+ log_info(LD_CONTROL, "Control connection %d has taken ownership of this "
+ "Tor instance.",
+ (int)(conn->_base.s));
+
+ send_control_done(conn);
+ return 0;
+}
+
/** Called when we get a MAPADDRESS command; try to bind all listed addresses,
* and report success or failure. */
static int
@@ -2010,8 +2032,8 @@ static const getinfo_item_t getinfo_items[] = {
"v2 networkstatus docs as retrieved from a DirPort."),
ITEM("dir/status-vote/current/consensus", dir,
"v3 Networkstatus consensus as retrieved from a DirPort."),
- PREFIX("exit-policy/default", policies,
- "The default value appended to the configured exit policy."),
+ ITEM("exit-policy/default", policies,
+ "The default value appended to the configured exit policy."),
PREFIX("ip-to-country/", geoip, "Perform a GEOIP lookup"),
{ NULL, NULL, NULL, 0 }
};
@@ -2842,6 +2864,43 @@ connection_control_reached_eof(control_connection_t *conn)
return 0;
}
+/** Shut down this Tor instance in the same way that SIGINT would, but
+ * with a log message appropriate for the loss of an owning controller. */
+static void
+lost_owning_controller(const char *owner_type, const char *loss_manner)
+{
+ int shutdown_slowly = server_mode(get_options());
+
+ log_notice(LD_CONTROL, "Owning controller %s has %s -- %s.",
+ owner_type, loss_manner,
+ shutdown_slowly ? "shutting down" : "exiting now");
+
+ /* XXXX Perhaps this chunk of code should be a separate function,
+ * called here and by process_signal(SIGINT). */
+
+ if (!shutdown_slowly) {
+ tor_cleanup();
+ exit(0);
+ }
+ /* XXXX This will close all listening sockets except control-port
+ * listeners. Perhaps we should close those too. */
+ hibernate_begin_shutdown();
+}
+
+/** Called when <b>conn</b> is being freed. */
+void
+connection_control_closed(control_connection_t *conn)
+{
+ tor_assert(conn);
+
+ conn->event_mask = 0;
+ control_update_global_event_mask();
+
+ if (conn->is_owning_control_connection) {
+ lost_owning_controller("connection", "closed");
+ }
+}
+
/** Return true iff <b>cmd</b> is allowable (or at least forgivable) at this
* stage of the protocol. */
static int
@@ -2997,6 +3056,9 @@ connection_control_process_inbuf(control_connection_t *conn)
return 0;
}
+ /* XXXX Why is this not implemented as a table like the GETINFO
+ * items are? Even handling the plus signs at the beginnings of
+ * commands wouldn't be very hard with proper macros. */
cmd_data_len = (uint32_t)data_len;
if (!strcasecmp(conn->incoming_cmd, "SETCONF")) {
if (handle_control_setconf(conn, cmd_data_len, args))
@@ -3022,6 +3084,9 @@ connection_control_process_inbuf(control_connection_t *conn)
} else if (!strcasecmp(conn->incoming_cmd, "SIGNAL")) {
if (handle_control_signal(conn, cmd_data_len, args))
return -1;
+ } else if (!strcasecmp(conn->incoming_cmd, "TAKEOWNERSHIP")) {
+ if (handle_control_takeownership(conn, cmd_data_len, args))
+ return -1;
} else if (!strcasecmp(conn->incoming_cmd, "MAPADDRESS")) {
if (handle_control_mapaddress(conn, cmd_data_len, args))
return -1;
@@ -3077,7 +3142,6 @@ control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp,
{
const char *status;
char extended_buf[96];
- int providing_reason=0;
if (!EVENT_IS_INTERESTING(EVENT_CIRCUIT_STATUS))
return 0;
tor_assert(circ);
@@ -3101,7 +3165,6 @@ control_event_circuit_status(origin_circuit_t *circ, circuit_status_event_t tp,
const char *reason_str = circuit_end_reason_to_control_string(reason_code);
char *reason = NULL;
size_t n=strlen(extended_buf);
- providing_reason=1;
if (!reason_str) {
reason = tor_malloc(16);
tor_snprintf(reason, 16, "UNKNOWN_%d", reason_code);
@@ -3884,6 +3947,75 @@ init_cookie_authentication(int enabled)
return 0;
}
+/** A copy of the process specifier of Tor's owning controller, or
+ * NULL if this Tor instance is not currently owned by a process. */
+static char *owning_controller_process_spec = NULL;
+
+/** A process-termination monitor for Tor's owning controller, or NULL
+ * if this Tor instance is not currently owned by a process. */
+static tor_process_monitor_t *owning_controller_process_monitor = NULL;
+
+/** Process-termination monitor callback for Tor's owning controller
+ * process. */
+static void
+owning_controller_procmon_cb(void *unused)
+{
+ (void)unused;
+
+ lost_owning_controller("process", "vanished");
+}
+
+/** Set <b>process_spec</b> as Tor's owning controller process.
+ * Exit on failure. */
+void
+monitor_owning_controller_process(const char *process_spec)
+{
+ const char *msg;
+
+ tor_assert((owning_controller_process_spec == NULL) ==
+ (owning_controller_process_monitor == NULL));
+
+ if (owning_controller_process_spec != NULL) {
+ if ((process_spec != NULL) && !strcmp(process_spec,
+ owning_controller_process_spec)) {
+ /* Same process -- return now, instead of disposing of and
+ * recreating the process-termination monitor. */
+ return;
+ }
+
+ /* We are currently owned by a process, and we should no longer be
+ * owned by it. Free the process-termination monitor. */
+ tor_process_monitor_free(owning_controller_process_monitor);
+ owning_controller_process_monitor = NULL;
+
+ tor_free(owning_controller_process_spec);
+ owning_controller_process_spec = NULL;
+ }
+
+ tor_assert((owning_controller_process_spec == NULL) &&
+ (owning_controller_process_monitor == NULL));
+
+ if (process_spec == NULL)
+ return;
+
+ owning_controller_process_spec = tor_strdup(process_spec);
+ owning_controller_process_monitor =
+ tor_process_monitor_new(tor_libevent_get_base(),
+ owning_controller_process_spec,
+ LD_CONTROL,
+ owning_controller_procmon_cb, NULL,
+ &msg);
+
+ if (owning_controller_process_monitor == NULL) {
+ log_err(LD_BUG, "Couldn't create process-termination monitor for "
+ "owning controller: %s. Exiting.",
+ msg);
+ owning_controller_process_spec = NULL;
+ tor_cleanup();
+ exit(0);
+ }
+}
+
/** Convert the name of a bootstrapping phase <b>s</b> into strings
* <b>tag</b> and <b>summary</b> suitable for display by the controller. */
static int
diff --git a/src/or/control.h b/src/or/control.h
index a73ed5d3c1..ddea4cd548 100644
--- a/src/or/control.h
+++ b/src/or/control.h
@@ -27,6 +27,8 @@ void control_ports_write_to_file(void);
int connection_control_finished_flushing(control_connection_t *conn);
int connection_control_reached_eof(control_connection_t *conn);
+void connection_control_closed(control_connection_t *conn);
+
int connection_control_process_inbuf(control_connection_t *conn);
#define EVENT_AUTHDIR_NEWDESCS 0x000D
@@ -72,6 +74,8 @@ smartlist_t *decode_hashed_passwords(config_line_t *passwords);
void disable_control_logging(void);
void enable_control_logging(void);
+void monitor_owning_controller_process(const char *process_spec);
+
void control_event_bootstrap(bootstrap_status_t status, int progress);
void control_event_bootstrap_problem(const char *warn, int reason);
diff --git a/src/or/directory.c b/src/or/directory.c
index eb99e9d081..ff0a5a427b 100644
--- a/src/or/directory.c
+++ b/src/or/directory.c
@@ -1900,7 +1900,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
router_get_trusteddirserver_by_digest(conn->identity_digest);
char *rejected_hdr = http_get_header(headers,
"X-Descriptor-Not-New: ");
- int rejected = 0;
if (rejected_hdr) {
if (!strcmp(rejected_hdr, "Yes")) {
log_info(LD_GENERAL,
@@ -1913,7 +1912,6 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
* last descriptor, not on the published time of the last
* descriptor. If those are different, that's a bad thing to
* do. -NM */
- rejected = 1;
}
tor_free(rejected_hdr);
}
diff --git a/src/or/dirvote.c b/src/or/dirvote.c
index 96e3df5cec..c6ce9f6776 100644
--- a/src/or/dirvote.c
+++ b/src/or/dirvote.c
@@ -1592,7 +1592,7 @@ networkstatus_compute_consensus(smartlist_t *votes,
* is the same flag as votes[j]->known_flags[b]. */
int *named_flag; /* Index of the flag "Named" for votes[j] */
int *unnamed_flag; /* Index of the flag "Unnamed" for votes[j] */
- int chosen_named_idx, chosen_unnamed_idx;
+ int chosen_named_idx;
strmap_t *name_to_id_map = strmap_new();
char conflict[DIGEST_LEN];
@@ -1610,7 +1610,6 @@ networkstatus_compute_consensus(smartlist_t *votes,
for (i = 0; i < smartlist_len(votes); ++i)
unnamed_flag[i] = named_flag[i] = -1;
chosen_named_idx = smartlist_string_pos(flags, "Named");
- chosen_unnamed_idx = smartlist_string_pos(flags, "Unnamed");
/* Build the flag index. */
SMARTLIST_FOREACH(votes, networkstatus_t *, v,
diff --git a/src/or/eventdns.c b/src/or/eventdns.c
index d06fb2cf21..42e16aec7a 100644
--- a/src/or/eventdns.c
+++ b/src/or/eventdns.c
@@ -1028,6 +1028,9 @@ request_parse(u8 *packet, ssize_t length, struct evdns_server_port *port, struct
GET16(answers);
GET16(authority);
GET16(additional);
+ (void)additional;
+ (void)authority;
+ (void)answers;
if (flags & 0x8000) return -1; /* Must not be an answer. */
flags &= 0x0110; /* Only RD and CD get preserved. */
@@ -2288,7 +2291,7 @@ _evdns_nameserver_add_impl(const struct sockaddr *address,
evtimer_set(&ns->timeout_event, nameserver_prod_callback, ns);
- ns->socket = socket(PF_INET, SOCK_DGRAM, 0);
+ ns->socket = socket(address->sa_family, SOCK_DGRAM, 0);
if (ns->socket < 0) { err = 1; goto out1; }
#ifdef WIN32
{
diff --git a/src/or/main.c b/src/or/main.c
index a8146e7993..adbde9044f 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -1164,7 +1164,10 @@ run_scheduled_events(time_t now)
* it's not comfortable with the number of available circuits.
*/
/* XXXX022 If our circuit build timeout is much lower than a second, maybe
- we should do this more often? */
+ * we should do this more often? -NM
+ * It can't be lower than 1.5 seconds currently; see
+ * circuit_build_times_min_timeout(). -RD
+ */
circuit_expire_building();
/** 3b. Also look at pending streams and prune the ones that 'began'
diff --git a/src/or/or.h b/src/or/or.h
index ae56dcd9b4..97fecd1500 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -1009,7 +1009,7 @@ typedef struct connection_t {
/* XXXX023 move this field, and all the listener-only fields (just
socket_family, I think), into a new listener_connection_t subtype. */
/** If the connection is a CONN_TYPE_AP_DNS_LISTENER, this field points
- * to the evdns_server_port is uses to listen to and answer connections. */
+ * to the evdns_server_port it uses to listen to and answer connections. */
struct evdns_server_port *dns_server_port;
/** Unique ID for measuring tunneled network status requests. */
@@ -1242,6 +1242,9 @@ typedef struct control_connection_t {
/** True if we have sent a protocolinfo reply on this connection. */
unsigned int have_sent_protocolinfo:1;
+ /** True if we have received a takeownership command on this
+ * connection. */
+ unsigned int is_owning_control_connection:1;
/** Amount of space allocated in incoming_cmd. */
uint32_t incoming_cmd_len;
@@ -2140,6 +2143,11 @@ typedef struct circuit_t {
* in time in order to indicate that a circuit shouldn't be used for new
* streams, but that it can stay alive as long as it has streams on it.
* That's a kludge we should fix.
+ *
+ * XXX023 The CBT code uses this field to record when HS-related
+ * circuits entered certain states. This usage probably won't
+ * interfere with this field's primary purpose, but we should
+ * document it more thoroughly to make sure of that.
*/
time_t timestamp_dirty;
@@ -2674,6 +2682,11 @@ typedef struct {
int DisablePredictedCircuits; /**< Boolean: does Tor preemptively
* make circuits in the background (0),
* or not (1)? */
+
+ /** Process specifier for a controller that ‘owns’ this Tor
+ * instance. Tor will terminate if its owning controller does. */
+ char *OwningControllerProcess;
+
int ShutdownWaitLength; /**< When we get a SIGINT and we're a server, how
* long do we wait before exiting? */
char *SafeLogging; /**< Contains "relay", "1", "0" (meaning no scrubbing). */
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index 77e11c2a07..a5423394f7 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -275,6 +275,10 @@ rend_client_send_introduction(origin_circuit_t *introcirc,
/* Now, we wait for an ACK or NAK on this circuit. */
introcirc->_base.purpose = CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT;
+ /* Set timestamp_dirty, because circuit_expire_building expects it
+ * to specify when a circuit entered the _C_INTRODUCE_ACK_WAIT
+ * state. */
+ introcirc->_base.timestamp_dirty = time(NULL);
return 0;
perm_err:
@@ -329,6 +333,10 @@ rend_client_introduction_acked(origin_circuit_t *circ,
circ->rend_data->onion_address, CIRCUIT_PURPOSE_C_REND_READY);
if (rendcirc) { /* remember the ack */
rendcirc->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
+ /* Set timestamp_dirty, because circuit_expire_building expects
+ * it to specify when a circuit entered the
+ * _C_REND_READY_INTRO_ACKED state. */
+ rendcirc->_base.timestamp_dirty = time(NULL);
} else {
log_info(LD_REND,"...Found no rend circ. Dropping on the floor.");
}
@@ -674,6 +682,9 @@ rend_client_rendezvous_acked(origin_circuit_t *circ, const uint8_t *request,
log_info(LD_REND,"Got rendezvous ack. This circuit is now ready for "
"rendezvous.");
circ->_base.purpose = CIRCUIT_PURPOSE_C_REND_READY;
+ /* Set timestamp_dirty, because circuit_expire_building expects it
+ * to specify when a circuit entered the _C_REND_READY state. */
+ circ->_base.timestamp_dirty = time(NULL);
/* XXXX023 This is a pretty brute-force approach. It'd be better to
* attach only the connections that are waiting on this circuit, rather
* than trying to attach them all. See comments bug 743. */