aboutsummaryrefslogtreecommitdiff
path: root/src/or
diff options
context:
space:
mode:
authorRoger Dingledine <arma@torproject.org>2005-01-17 18:13:09 +0000
committerRoger Dingledine <arma@torproject.org>2005-01-17 18:13:09 +0000
commitd2400a5afd70b009b632b307205273fc25c8cd92 (patch)
tree5a25d9d3a30fb61f43c53387397e8529544775c2 /src/or
parent9c8c90ec2f2ae0d83effbaa3a13077dfc9dd975c (diff)
downloadtor-d2400a5afd70b009b632b307205273fc25c8cd92.tar
tor-d2400a5afd70b009b632b307205273fc25c8cd92.tar.gz
Introduce a notion of 'internal' circs, which are chosen without regard
to the exit policy of the last hop. Intro and rendezvous circs must be internal circs, to avoid leaking information. Resolve and connect streams can use internal circs if they want. New circuit pooling algorithm: make sure to have enough circs around to satisfy any predicted ports, and also make sure to have 2 internal circs around if we've required internal circs lately (with high uptime if we've seen that lately). Split NewCircuitPeriod config option into NewCircuitPeriod (30 secs), which describes how often we retry making new circuits if current ones are dirty, and MaxCircuitDirtiness (10 mins), which describes how long we're willing to make use of an already-dirty circuit. Once rendezvous circuits are established, keep using the same circuit as long as you attach a new stream to it at least every 10 minutes. (So web browsing doesn't require you to build new rend circs every 30 seconds.) Cannibalize GENERAL circs to be C_REND, C_INTRO, S_INTRO, and S_REND circ as necessary, if there are any completed ones lying around when we try to launch one. Re-instate the ifdef's to use version-0 style introduce cells, since there was yet another bug in handling version-1 style. We'll try switching over again after 0.0.9 is obsolete. Bugfix: when choosing an exit node for a new non-internal circ, don't take into account whether it'll be useful for any pending x.onion addresses -- it won't. Bugfix: we weren't actually publishing the hidden service descriptor when it became dirty. So we only published it every 20 minutes or so, which means when you first start your Tor, the hidden service will seem broken. svn:r3360
Diffstat (limited to 'src/or')
-rw-r--r--src/or/circuitbuild.c43
-rw-r--r--src/or/circuitlist.c38
-rw-r--r--src/or/circuituse.c241
-rw-r--r--src/or/config.c1
-rw-r--r--src/or/connection_edge.c15
-rw-r--r--src/or/main.c8
-rw-r--r--src/or/or.h20
-rw-r--r--src/or/relay.c2
-rw-r--r--src/or/rendclient.c36
-rw-r--r--src/or/rendservice.c28
-rw-r--r--src/or/rephist.c37
11 files changed, 306 insertions, 163 deletions
diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c
index d7e00581b..868740fe0 100644
--- a/src/or/circuitbuild.c
+++ b/src/or/circuitbuild.c
@@ -23,7 +23,7 @@ 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 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 count_acceptable_routers(smartlist_t *routers);
@@ -82,7 +82,9 @@ circuit_list_path(circuit_t *circ, int verbose)
elements = smartlist_create();
if (verbose) {
- tor_snprintf(buf, sizeof(buf)-1, "circ (length %d, exit %s):",
+ tor_snprintf(buf, sizeof(buf)-1, "%s%s circ (length %d, exit %s):",
+ circ->build_state->is_internal ? "internal" : "exit",
+ circ->build_state->need_uptime ? " (high-uptime)" : "",
circ->build_state->desired_path_len,
circ->build_state->chosen_exit_name);
smartlist_add(elements, tor_strdup(buf));
@@ -236,7 +238,7 @@ void circuit_dump_by_conn(connection_t *conn, int severity) {
*/
circuit_t *
circuit_establish_circuit(uint8_t purpose, const char *exit_digest,
- int need_uptime, int need_capacity) {
+ int need_uptime, int need_capacity, int internal) {
routerinfo_t *firsthop;
connection_t *n_conn;
circuit_t *circ;
@@ -244,7 +246,7 @@ circuit_establish_circuit(uint8_t purpose, const char *exit_digest,
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);
+ need_uptime, need_capacity, internal);
circ->purpose = purpose;
if (! circ->build_state) {
@@ -951,6 +953,7 @@ choose_good_exit_server_general(routerlist_t *dir, int need_uptime,
if (carray[j]->type != CONN_TYPE_AP ||
carray[j]->state != AP_CONN_STATE_CIRCUIT_WAIT ||
carray[j]->marked_for_close ||
+ connection_edge_is_rendezvous_stream(carray[j]) ||
circuit_stream_is_being_handled(carray[j], 0, MIN_CIRCUITS_HANDLING_STREAM))
continue; /* Skip everything but APs in CIRCUIT_WAIT */
if (connection_ap_can_use_exit(carray[j], router)) {
@@ -1080,7 +1083,7 @@ choose_good_exit_server(uint8_t purpose, routerlist_t *dir,
*/
static cpath_build_state_t *
onion_new_cpath_build_state(uint8_t purpose, const char *exit_digest,
- int need_uptime, int need_capacity)
+ int need_uptime, int need_capacity, int internal)
{
routerlist_t *rl;
int r;
@@ -1097,6 +1100,7 @@ onion_new_cpath_build_state(uint8_t purpose, const char *exit_digest,
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);
@@ -1121,6 +1125,35 @@ onion_new_cpath_build_state(uint8_t purpose, const char *exit_digest,
return info;
}
+/** 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.
+ */
+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;
+ 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->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;
+ }
+ return 0;
+}
+
/** Return the number of routers in <b>routers</b> that are currently up
* and available for building circuits through.
*/
diff --git a/src/or/circuitlist.c b/src/or/circuitlist.c
index 11c7be3e8..643e360c3 100644
--- a/src/or/circuitlist.c
+++ b/src/or/circuitlist.c
@@ -292,41 +292,29 @@ circuit_t *circuit_get_rendezvous(const char *cookie)
return NULL;
}
-/** Count the number of circs originating here that aren't open, and
- * that have the specified <b>purpose</b>. */
-int circuit_count_building(uint8_t purpose) {
- circuit_t *circ;
- int num=0;
-
- for (circ=global_circuitlist;circ;circ = circ->next) {
- if (CIRCUIT_IS_ORIGIN(circ) &&
- circ->state != CIRCUIT_STATE_OPEN &&
- circ->purpose == purpose &&
- !circ->marked_for_close)
- num++;
- }
- return num;
-}
-
-/** Return the circuit that is open, has specified <b>purpose</b>,
- * has a timestamp_dirty value of 0, and was created most recently,
- * or NULL if no circuit fits this description.
+/** Return a circuit that is open, has specified <b>purpose</b>,
+ * has a timestamp_dirty value of 0, and is uptime/capacity/internal
+ * if required; or NULL if no circuit fits this description.
*/
circuit_t *
-circuit_get_youngest_clean_open(uint8_t purpose) {
+circuit_get_clean_open(uint8_t purpose, int need_uptime,
+ int need_capacity, int internal) {
circuit_t *circ;
- circuit_t *youngest=NULL;
- for (circ=global_circuitlist;circ;circ = circ->next) {
+ log_fn(LOG_DEBUG,"Hunting for a circ to cannibalize: purpose %d, uptime %d, capacity %d, internal %d", purpose, need_uptime, need_capacity, internal);
+
+ for (circ=global_circuitlist; circ; circ = circ->next) {
if (CIRCUIT_IS_ORIGIN(circ) &&
circ->state == CIRCUIT_STATE_OPEN &&
!circ->marked_for_close &&
circ->purpose == purpose &&
!circ->timestamp_dirty &&
- (!youngest || youngest->timestamp_created < circ->timestamp_created))
- youngest = circ;
+ (!need_uptime || circ->build_state->need_uptime) &&
+ (!need_capacity || circ->build_state->need_capacity) &&
+ (!internal || circ->build_state->is_internal))
+ return circ;
}
- return youngest;
+ return NULL;
}
/** Mark <b>circ</b> to be closed next time we call
diff --git a/src/or/circuituse.c b/src/or/circuituse.c
index 35a83fd45..a3adde59b 100644
--- a/src/or/circuituse.c
+++ b/src/or/circuituse.c
@@ -35,6 +35,9 @@ static int circuit_is_acceptable(circuit_t *circ,
time_t now)
{
routerinfo_t *exitrouter;
+ tor_assert(circ);
+ tor_assert(conn);
+ tor_assert(conn->socks_request);
if (!CIRCUIT_IS_ORIGIN(circ))
return 0; /* this circ doesn't start at us */
@@ -61,40 +64,36 @@ static int circuit_is_acceptable(circuit_t *circ,
if (purpose == CIRCUIT_PURPOSE_C_GENERAL)
if (circ->timestamp_dirty &&
- circ->timestamp_dirty+get_options()->NewCircuitPeriod <= now)
+ circ->timestamp_dirty+get_options()->MaxCircuitDirtiness <= now)
return 0;
- if (conn) {
- /* decide if this circ is suitable for this conn */
+ /* decide if this circ is suitable for this conn */
- /* for rend circs, circ->cpath->prev is not the last router in the
- * circuit, it's the magical extra bob hop. so just check the nickname
- * of the one we meant to finish at.
- */
- exitrouter = router_get_by_digest(circ->build_state->chosen_exit_digest);
+ /* for rend circs, circ->cpath->prev is not the last router in the
+ * circuit, it's the magical extra bob hop. so just check the nickname
+ * of the one we meant to finish at.
+ */
+ exitrouter = router_get_by_digest(circ->build_state->chosen_exit_digest);
- if (!exitrouter) {
- log_fn(LOG_INFO,"Skipping broken circ (exit router vanished)");
- return 0; /* this circuit is screwed and doesn't know it yet */
- }
+ if (!exitrouter) {
+ log_fn(LOG_INFO,"Skipping broken circ (exit router vanished)");
+ return 0; /* this circuit is screwed and doesn't know it yet */
+ }
- if (!circ->build_state->need_uptime &&
- smartlist_string_num_isin(get_options()->LongLivedPorts,
- conn->socks_request->port))
- return 0;
+ if (!circ->build_state->need_uptime &&
+ smartlist_string_num_isin(get_options()->LongLivedPorts,
+ conn->socks_request->port))
+ return 0;
- if (conn->socks_request &&
- conn->socks_request->command == SOCKS_COMMAND_RESOLVE) {
- } else if (purpose == CIRCUIT_PURPOSE_C_GENERAL) {
+ if (conn->socks_request->command == SOCKS_COMMAND_CONNECT) {
+ if (purpose == CIRCUIT_PURPOSE_C_GENERAL) {
if (!connection_ap_can_use_exit(conn, exitrouter)) {
/* can't exit from this router */
return 0;
}
} else { /* not general */
- if (rend_cmp_service_ids(conn->rend_query, circ->rend_query) &&
- (circ->rend_query[0] || purpose != CIRCUIT_PURPOSE_C_REND_JOINED)) {
- /* this circ is not for this conn, and it's not suitable
- * for cannibalizing either */
+ if (rend_cmp_service_ids(conn->rend_query, circ->rend_query)) {
+ /* this circ is not for this conn */
return 0;
}
}
@@ -178,9 +177,8 @@ circuit_get_best(connection_t *conn, int must_be_open, uint8_t purpose)
return best;
}
-/** Circuits that were born at the end of their second might be expired
- * after 30.1 seconds; circuits born at the beginning might be expired
- * after closer to 31 seconds.
+/** If we find a circuit that isn't open yet and was born this many
+ * seconds ago, then assume something went wrong, and cull it.
*/
#define MIN_SECONDS_BEFORE_EXPIRING_CIRC 30
@@ -289,7 +287,7 @@ int circuit_stream_is_being_handled(connection_t *conn, uint16_t port, int min)
!circ->marked_for_close &&
circ->purpose == CIRCUIT_PURPOSE_C_GENERAL &&
(!circ->timestamp_dirty ||
- circ->timestamp_dirty + get_options()->NewCircuitPeriod < now)) {
+ circ->timestamp_dirty + get_options()->MaxCircuitDirtiness < now)) {
exitrouter = router_get_by_digest(circ->build_state->chosen_exit_digest);
if (exitrouter &&
(!need_uptime || circ->build_state->need_uptime) &&
@@ -305,6 +303,65 @@ int circuit_stream_is_being_handled(connection_t *conn, uint16_t port, int min)
return 0;
}
+/** Don't keep more than 10 unused open circuits around. */
+#define MAX_UNUSED_OPEN_CIRCUITS 10
+
+/** Figure out how many circuits we have open that are clean. Make
+ * sure it's enough for all the upcoming behaviors we predict we'll have.
+ * But if we have too many, close the not-so-useful ones.
+ */
+static void
+circuit_predict_and_launch_new(void)
+{
+ circuit_t *circ;
+ int num=0, num_internal=0, num_uptime_internal=0;
+ int hidserv_needs_uptime=0, hidserv_needs_capacity=1;
+ int port_needs_uptime=0, port_needs_capacity=1;
+ int need_ports, need_hidserv;
+ time_t now = time(NULL);
+
+ /* check if we know of a port that's been requested recently
+ * and no circuit is currently available that can handle it. */
+ need_ports = !circuit_all_predicted_ports_handled(now, &port_needs_uptime,
+ &port_needs_capacity);
+
+ need_hidserv = rep_hist_get_predicted_hidserv(now, &hidserv_needs_uptime,
+ &hidserv_needs_capacity);
+
+ for (circ=global_circuitlist;circ;circ = circ->next) {
+ if (!CIRCUIT_IS_ORIGIN(circ))
+ continue;
+ if (circ->marked_for_close)
+ continue; /* don't mess with marked circs */
+ if (circ->timestamp_dirty)
+ continue; /* only count clean circs */
+ if (circ->purpose != CIRCUIT_PURPOSE_C_GENERAL)
+ continue; /* only pay attention to general-purpose circs */
+ num++;
+ if (circ->build_state->is_internal)
+ num_internal++;
+ if (circ->build_state->need_uptime && circ->build_state->is_internal)
+ num_uptime_internal++;
+ }
+
+ if (num < MAX_UNUSED_OPEN_CIRCUITS) {
+ /* perhaps we want another */
+ 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,
+ 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,
+ hidserv_needs_uptime, hidserv_needs_capacity, 1);
+ }
+ }
+}
+
/** Build a new test circuit every 5 minutes */
#define TESTING_CIRCUIT_INTERVAL 300
@@ -315,8 +372,6 @@ int circuit_stream_is_being_handled(connection_t *conn, uint16_t port, int min)
*/
void circuit_build_needed_circs(time_t now) {
static long time_to_new_circuit = 0;
- circuit_t *circ;
- int need_uptime=0, need_capacity=1;
/* launch a new circ for any pending streams that need one */
connection_ap_attach_pending();
@@ -325,8 +380,6 @@ void circuit_build_needed_circs(time_t now) {
if (has_fetched_directory)
rend_services_introduce();
- circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL);
-
if (time_to_new_circuit < now) {
circuit_reset_failure_count(1);
time_to_new_circuit = now + get_options()->NewCircuitPeriod;
@@ -334,37 +387,17 @@ void circuit_build_needed_circs(time_t now) {
client_dns_clean();
circuit_expire_old_circuits();
+#if 0 /* disable for now, until predict-and-launch-new can cull leftovers */
+ circ = circuit_get_youngest_clean_open(CIRCUIT_PURPOSE_C_GENERAL);
if (get_options()->RunTesting &&
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);
+ circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL, 0, 0, 0);
}
- }
-
-#if 0
-/** How many simultaneous in-progress general-purpose circuits do we
- * want to be building at once, if there are no open general-purpose
- * circuits?
- */
-#define CIRCUIT_MIN_BUILDING_GENERAL 5
- /* if there's no open circ, and less than 5 are on the way,
- * go ahead and try another. */
- if (!circ && circuit_count_building(CIRCUIT_PURPOSE_C_GENERAL)
- < CIRCUIT_MIN_BUILDING_GENERAL) {
- circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL);
- }
#endif
-
- /* if we know of a port that's been requested recently and no
- * circuit is currently available that can handle it, start one
- * for that too. */
- if (!circuit_all_predicted_ports_handled(now, &need_uptime, &need_capacity)) {
- circuit_launch_by_identity(CIRCUIT_PURPOSE_C_GENERAL, NULL,
- need_uptime, need_capacity);
}
-
- /* XXX count idle rendezvous circs and build more */
+ circuit_predict_and_launch_new();
}
/** If the stream <b>conn</b> is a member of any of the linked
@@ -469,24 +502,14 @@ void circuit_about_to_close_connection(connection_t *conn) {
} /* end switch */
}
-/** Don't keep more than 10 unused open circuits around. */
-#define MAX_UNUSED_OPEN_CIRCUITS 10
-
/** Find each circuit that has been dirty for too long, and has
* no streams on it: mark it for close.
- *
- * Also, if there are more than MAX_UNUSED_OPEN_CIRCUITS open and
- * unused circuits, then mark the excess circs for close.
*/
static void
circuit_expire_old_circuits(void)
{
circuit_t *circ;
time_t now = time(NULL);
- smartlist_t *unused_open_circs;
- int i;
-
- unused_open_circs = smartlist_create();
for (circ = global_circuitlist; circ; circ = circ->next) {
if (circ->marked_for_close)
@@ -495,8 +518,8 @@ circuit_expire_old_circuits(void)
* on it, mark it for close.
*/
if (circ->timestamp_dirty &&
- circ->timestamp_dirty + get_options()->NewCircuitPeriod < now &&
- !circ->p_conn && /* we're the origin */
+ circ->timestamp_dirty + get_options()->MaxCircuitDirtiness < now &&
+ CIRCUIT_IS_ORIGIN(circ) &&
!circ->p_streams /* nothing attached */ ) {
log_fn(LOG_DEBUG,"Closing n_circ_id %d (dirty %d secs ago, purp %d)",circ->n_circ_id,
(int)(now - circ->timestamp_dirty), circ->purpose);
@@ -512,23 +535,9 @@ circuit_expire_old_circuits(void)
log_fn(LOG_DEBUG,"Closing circuit that has been unused for %d seconds.",
(int)(now - circ->timestamp_created));
circuit_mark_for_close(circ);
- } else {
- /* Also, gather a list of open unused general circuits that we created.
- * Because we add elements to the front of global_circuitlist,
- * the last elements of unused_open_circs will be the oldest
- * ones.
- */
- smartlist_add(unused_open_circs, circ);
}
}
}
- for (i = MAX_UNUSED_OPEN_CIRCUITS; i < smartlist_len(unused_open_circs); ++i) {
- circuit_t *circ = smartlist_get(unused_open_circs, i);
- log_fn(LOG_DEBUG,"Expiring excess clean circ (n_circ_id %d, purp %d)",
- circ->n_circ_id, circ->purpose);
- circuit_mark_for_close(circ);
- }
- smartlist_free(unused_open_circs);
}
/** The circuit <b>circ</b> has just become open. Take the next
@@ -646,13 +655,52 @@ static int did_circs_fail_last_period = 0;
circuit_t *
circuit_launch_by_identity(uint8_t purpose, const char *exit_digest,
- int need_uptime, int need_capacity)
+ int need_uptime, int need_capacity, int internal)
{
+ circuit_t *circ;
+
if (!has_fetched_directory) {
log_fn(LOG_DEBUG,"Haven't fetched directory yet; canceling circuit launch.");
return NULL;
}
+ if (purpose != CIRCUIT_PURPOSE_C_GENERAL) {
+ /* see if there are appropriate circs available to cannibalize. */
+ if ((circ = circuit_get_clean_open(CIRCUIT_PURPOSE_C_GENERAL, need_uptime,
+ need_capacity, internal))) {
+ log_fn(LOG_INFO,"Cannibalizing circ '%s' for purpose %d",
+ circ->build_state->chosen_exit_name, purpose);
+ circ->purpose = purpose;
+ /* reset the birth date of this circ, else expire_building
+ * will see it and think it's been trying to build since it
+ * began. */
+ circ->timestamp_created = time(NULL);
+ switch (purpose) {
+ case CIRCUIT_PURPOSE_C_ESTABLISH_REND:
+ /* it's ready right now */
+ /* XXX should we call control_event_circuit_status() here? */
+ rend_client_rendcirc_has_opened(circ);
+ break;
+ case CIRCUIT_PURPOSE_S_ESTABLISH_INTRO:
+ /* it's ready right now */
+ rend_service_intro_has_opened(circ);
+ break;
+ 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)
+ return NULL;
+ break;
+ default:
+ log_fn(LOG_WARN,"Bug: unexpected purpose %d when cannibalizing a general circ.",
+ purpose);
+ return NULL;
+ }
+ return circ;
+ }
+ }
+
if (did_circs_fail_last_period &&
n_circuit_failures > MAX_CIRCUIT_FAILURES) {
/* too many failed circs in a row. don't try. */
@@ -662,13 +710,13 @@ circuit_launch_by_identity(uint8_t purpose, const char *exit_digest,
/* try a circ. if it fails, circuit_mark_for_close will increment n_circuit_failures */
return circuit_establish_circuit(purpose, exit_digest,
- need_uptime, need_capacity);
+ need_uptime, need_capacity, internal);
}
/** Launch a new circuit and return a pointer to it. Return NULL if you failed. */
circuit_t *
circuit_launch_by_nickname(uint8_t purpose, const char *exit_nickname,
- int need_uptime, int need_capacity)
+ int need_uptime, int need_capacity, int internal)
{
const char *digest = NULL;
@@ -681,7 +729,7 @@ circuit_launch_by_nickname(uint8_t purpose, const char *exit_nickname,
digest = r->identity_digest;
}
return circuit_launch_by_identity(purpose, digest,
- need_uptime, need_capacity);
+ need_uptime, need_capacity, internal);
}
/** Record another failure at opening a general circuit. When we have
@@ -762,6 +810,7 @@ circuit_get_open_circ_or_launch(connection_t *conn,
if (!circ) {
char *exitname=NULL;
uint8_t new_circ_purpose;
+ int is_internal;
if (desired_circuit_purpose == CIRCUIT_PURPOSE_C_INTRODUCE_ACK_WAIT) {
/* need to pick an intro point */
@@ -801,13 +850,18 @@ circuit_get_open_circ_or_launch(connection_t *conn,
else
new_circ_purpose = desired_circuit_purpose;
- circ = circuit_launch_by_nickname(new_circ_purpose, exitname, need_uptime, 1);
+ is_internal = (new_circ_purpose != CIRCUIT_PURPOSE_C_GENERAL || is_resolve);
+ circ = circuit_launch_by_nickname(new_circ_purpose, exitname, need_uptime,
+ 1, is_internal);
tor_free(exitname);
- if (circ &&
- desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) {
- /* then write the service_id into circ */
- strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+ if (desired_circuit_purpose != CIRCUIT_PURPOSE_C_GENERAL) {
+ /* help predict this next time */
+ rep_hist_note_used_hidserv(time(NULL), need_uptime, 1);
+ if (circ) {
+ /* write the service_id into circ */
+ strlcpy(circ->rend_query, conn->rend_query, sizeof(circ->rend_query));
+ }
}
}
if (!circ)
@@ -914,6 +968,12 @@ int connection_ap_handshake_attach_circuit(connection_t *conn) {
/* one is already established, attach */
log_fn(LOG_INFO,"rend joined circ %d already here. attaching. (stream %d sec old)",
rendcirc->n_circ_id, conn_age);
+ /* Mark rendezvous circuits as 'newly dirty' every time you use
+ * them, since the process of rebuilding a rendezvous circ is so
+ * expensive. There is a tradeoffs between linkability and
+ * feasibility, at this point.
+ */
+ rendcirc->timestamp_dirty = time(NULL);
link_apconn_to_circ(conn, rendcirc);
if (connection_ap_handshake_send_begin(conn, rendcirc) < 0)
return 0; /* already marked, let them fade away */
@@ -947,7 +1007,6 @@ int connection_ap_handshake_attach_circuit(connection_t *conn) {
if (introcirc->state == CIRCUIT_STATE_OPEN) {
log_fn(LOG_INFO,"found open intro circ %d (rend %d); sending introduction. (stream %d sec old)",
introcirc->n_circ_id, rendcirc->n_circ_id, conn_age);
- /* XXX here we should cannibalize the rend circ if it's a zero service id */
if (rend_client_send_introduction(introcirc, rendcirc) < 0) {
return -1;
}
diff --git a/src/or/config.c b/src/or/config.c
index ca756a436..353f30645 100644
--- a/src/or/config.c
+++ b/src/or/config.c
@@ -145,6 +145,7 @@ static config_var_t config_vars[] = {
VAR("AccountingMax", MEMUNIT, AccountingMax, "0 bytes"),
VAR("Nickname", STRING, Nickname, NULL),
VAR("NewCircuitPeriod", INTERVAL, NewCircuitPeriod, "30 seconds"),
+ VAR("MaxCircuitDirtiness", INTERVAL, MaxCircuitDirtiness, "10 minutes"),
VAR("NumCpus", UINT, NumCpus, "1"),
VAR("ORPort", UINT, ORPort, "0"),
VAR("ORBindAddress", LINELIST, ORBindAddress, NULL),
diff --git a/src/or/connection_edge.c b/src/or/connection_edge.c
index 65aaefb8b..1d5c4ad39 100644
--- a/src/or/connection_edge.c
+++ b/src/or/connection_edge.c
@@ -281,7 +281,7 @@ void connection_ap_expire_beginning(void) {
* current streams on it to survive if they can: make it
* unattractive to use for new streams */
tor_assert(circ->timestamp_dirty);
- circ->timestamp_dirty -= options->NewCircuitPeriod;
+ circ->timestamp_dirty -= options->MaxCircuitDirtiness;
/* give our stream another 15 seconds to try */
conn->timestamp_lastread += 15;
/* attaching to a dirty circuit is fine */
@@ -403,14 +403,15 @@ static int connection_ap_handshake_process_socks(connection_t *conn) {
conn->hold_open_until_flushed = 1;
return 0;
}
- }
-
- if (socks->command == SOCKS_COMMAND_CONNECT && socks->port == 0) {
- log_fn(LOG_NOTICE,"Application asked to connect to port 0. Refusing.");
- return -1;
+ rep_hist_note_used_resolve(time(NULL)); /* help predict this next time */
+ } else { /* socks->command == SOCKS_COMMAND_CONNECT */
+ if (socks->port == 0) {
+ log_fn(LOG_NOTICE,"Application asked to connect to port 0. Refusing.");
+ return -1;
+ }
+ rep_hist_note_used_port(socks->port, time(NULL)); /* help predict this next time */
}
conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
- rep_hist_note_used_port(socks->port, time(NULL)); /* help predict this next time */
return connection_ap_handshake_attach_circuit(conn);
} else {
/* it's a hidden-service request */
diff --git a/src/or/main.c b/src/or/main.c
index 8c0a1d170..54e6d3be6 100644
--- a/src/or/main.c
+++ b/src/or/main.c
@@ -528,8 +528,8 @@ void directory_all_unreachable(time_t now) {
while ((conn = connection_get_by_type_state(CONN_TYPE_AP,
AP_CONN_STATE_CIRCUIT_WAIT))) {
conn->has_sent_end = 1; /* it's not connected anywhere, so no need to end */
- log_fn(LOG_NOTICE,"Network down? Failing connection to '%s'.",
- conn->socks_request->address);
+ log_fn(LOG_NOTICE,"Network down? Failing connection to '%s:%d'.",
+ conn->socks_request->address, conn->socks_request->port);
connection_mark_for_close(conn);
}
}
@@ -818,7 +818,7 @@ static void run_scheduled_events(time_t now) {
/** 4. Every second, we try a new circuit if there are no valid
* circuits. Every NewCircuitPeriod seconds, we expire circuits
- * that became dirty more than NewCircuitPeriod seconds ago,
+ * that became dirty more than MaxCircuitDirtiness seconds ago,
* and we make a new circ if there are no clean circuits.
*/
if (has_fetched_directory && !we_are_hibernating())
@@ -833,7 +833,7 @@ static void run_scheduled_events(time_t now) {
circuit_close_all_marked();
/** 7. And upload service descriptors if necessary. */
- if (!we_are_hibernating())
+ if (has_fetched_directory && !we_are_hibernating())
rend_consider_services_upload(now);
/** 8. and blow away any connections that need to die. have to do this now,
diff --git a/src/or/or.h b/src/or/or.h
index 2479df984..aecd5e65d 100644
--- a/src/or/or.h
+++ b/src/or/or.h
@@ -731,6 +731,8 @@ typedef struct {
int need_uptime;
/** Whether every node in the circ must have adequate capacity. */
int need_capacity;
+ /** Whether the last hop was picked with exiting in mind. */
+ int is_internal;
/** The crypt_path_t to append after rendezvous: used for rendezvous. */
struct crypt_path_t *pending_final_cpath;
/** How many times has building a circuit for this task failed? */
@@ -955,6 +957,8 @@ typedef struct {
* them? */
int NewCircuitPeriod; /**< How long do we use a circuit before building
* a new one? */
+ int MaxCircuitDirtiness; /**< Never use circs that were first used more than
+ this interval ago. */
uint64_t BandwidthRate; /**< How much bandwidth, on average, are we willing to
* use in a second? */
uint64_t BandwidthBurst; /**< How much bandwidth, at maximum, are we willing to
@@ -1052,7 +1056,7 @@ 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,
- int need_uptime, int need_capacity);
+ int need_uptime, int need_capacity, int internal);
void circuit_n_conn_done(connection_t *or_conn, int status);
int circuit_send_next_onion_skin(circuit_t *circ);
int circuit_extend(cell_t *cell, circuit_t *circ);
@@ -1063,6 +1067,7 @@ int onionskin_answer(circuit_t *circ, unsigned char *payload, unsigned char *key
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);
void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
/********************************* circuitlist.c ***********************/
@@ -1076,8 +1081,8 @@ circuit_t *circuit_get_by_rend_query_and_purpose(const char *rend_query, uint8_t
circuit_t *circuit_get_next_by_pk_and_purpose(circuit_t *start,
const char *digest, uint8_t purpose);
circuit_t *circuit_get_rendezvous(const char *cookie);
-int circuit_count_building(uint8_t purpose);
-circuit_t *circuit_get_youngest_clean_open(uint8_t purpose);
+circuit_t *circuit_get_clean_open(uint8_t purpose, int need_uptime,
+ int need_capacity, int internal);
int _circuit_mark_for_close(circuit_t *circ);
#define circuit_mark_for_close(c) \
@@ -1106,9 +1111,9 @@ void circuit_about_to_close_connection(connection_t *conn);
void circuit_has_opened(circuit_t *circ);
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 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 need_uptime, int need_capacity, int is_internal);
void circuit_reset_failure_count(int timeout);
int connection_ap_handshake_attach_circuit(connection_t *conn);
@@ -1472,8 +1477,13 @@ void rep_hist_note_bytes_written(int num_bytes, time_t when);
int rep_hist_bandwidth_assess(void);
char *rep_hist_get_bandwidth_lines(void);
void rep_history_clean(time_t before);
+
void rep_hist_note_used_port(uint16_t port, time_t now);
smartlist_t *rep_hist_get_predicted_ports(time_t now);
+void rep_hist_note_used_hidserv(time_t now, int need_uptime, int need_capacity);
+int rep_hist_get_predicted_hidserv(time_t now, int *need_uptime, int *need_capacity);
+void rep_hist_note_used_resolve(time_t now);
+int rep_hist_get_predicted_resolve(time_t now);
/********************************* rendclient.c ***************************/
diff --git a/src/or/relay.c b/src/or/relay.c
index 661280c57..0a2042d6a 100644
--- a/src/or/relay.c
+++ b/src/or/relay.c
@@ -547,7 +547,7 @@ connection_edge_process_relay_cell_not_open(
conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
circuit_detach_stream(circ,conn);
tor_assert(circ->timestamp_dirty);
- circ->timestamp_dirty -= get_options()->NewCircuitPeriod;
+ circ->timestamp_dirty -= get_options()->MaxCircuitDirtiness;
/* make sure not to expire/retry the stream quite yet */
conn->timestamp_lastread = time(NULL);
if (connection_ap_handshake_attach_circuit(conn) >= 0)
diff --git a/src/or/rendclient.c b/src/or/rendclient.c
index be5489524..9ae2b2188 100644
--- a/src/or/rendclient.c
+++ b/src/or/rendclient.c
@@ -93,11 +93,22 @@ rend_client_send_introduction(circuit_t *introcirc, circuit_t *rendcirc) {
}
/* write the remaining items into tmp */
+#if 0
tmp[0] = 1; /* version 1 of the cell format */
- strncpy(tmp+1, rendcirc->build_state->chosen_exit_name, (MAX_HEX_NICKNAME_LEN+1)); /* nul pads */
+ /* nul pads */
+ strncpy(tmp+1, rendcirc->build_state->chosen_exit_name, (MAX_HEX_NICKNAME_LEN+1));
memcpy(tmp+1+MAX_HEX_NICKNAME_LEN+1, rendcirc->rend_cookie, REND_COOKIE_LEN);
+#else
+ strncpy(tmp, rendcirc->build_state->chosen_exit_name, (MAX_NICKNAME_LEN+1)); /* nul pads */
+ memcpy(tmp+MAX_NICKNAME_LEN+1, rendcirc->rend_cookie, REND_COOKIE_LEN);
+#endif
if (crypto_dh_get_public(cpath->handshake_state,
+#if 0
tmp+1+MAX_HEX_NICKNAME_LEN+1+REND_COOKIE_LEN,
+#else
+ tmp+MAX_NICKNAME_LEN+1+REND_COOKIE_LEN,
+#endif
+
DH_KEY_LEN)<0) {
log_fn(LOG_WARN, "Couldn't extract g^x");
goto err;
@@ -106,7 +117,11 @@ rend_client_send_introduction(circuit_t *introcirc, circuit_t *rendcirc) {
/*XXX maybe give crypto_pk_public_hybrid_encrypt a max_len arg,
* to avoid buffer overflows? */
r = crypto_pk_public_hybrid_encrypt(entry->parsed->pk, payload+DIGEST_LEN, tmp,
+#if 0
1+MAX_HEX_NICKNAME_LEN+1+REND_COOKIE_LEN+DH_KEY_LEN,
+#else
+ MAX_NICKNAME_LEN+1+REND_COOKIE_LEN+DH_KEY_LEN,
+#endif
PK_PKCS1_OAEP_PADDING, 0);
if (r<0) {
log_fn(LOG_WARN,"hybrid pk encrypt failed.");
@@ -176,11 +191,13 @@ rend_client_introduction_acked(circuit_t *circ,
/* Locate the rend circ which is waiting to hear about this ack,
* and tell it.
*/
- log_fn(LOG_INFO,"Received ack. Telling rend circ.");
+ log_fn(LOG_INFO,"Received ack. Telling rend circ...");
rendcirc = circuit_get_by_rend_query_and_purpose(
circ->rend_query, CIRCUIT_PURPOSE_C_REND_READY);
if (rendcirc) { /* remember the ack */
rendcirc->purpose = CIRCUIT_PURPOSE_C_REND_READY_INTRO_ACKED;
+ } else {
+ log_fn(LOG_INFO,"...Found no rend circ. Dropping on the floor.");
}
/* close the circuit: we won't need it anymore. */
circ->purpose = CIRCUIT_PURPOSE_C_INTRODUCE_ACKED;
@@ -199,7 +216,8 @@ rend_client_introduction_acked(circuit_t *circ,
routerinfo_t *r;
nickname = rend_client_get_random_intro(circ->rend_query);
tor_assert(nickname);
- log_fn(LOG_INFO,"Got nack for %s from %s, extending to %s.", circ->rend_query, circ->build_state->chosen_exit_name, nickname);
+ log_fn(LOG_INFO,"Got nack for %s from %s, extending to %s.",
+ circ->rend_query, circ->build_state->chosen_exit_name, nickname);
if (!(r = router_get_by_nickname(nickname))) {
log_fn(LOG_WARN, "Advertised intro point '%s' for %s is not known. Closing.",
nickname, circ->rend_query);
@@ -209,18 +227,8 @@ rend_client_introduction_acked(circuit_t *circ,
}
log_fn(LOG_INFO, "Chose new intro point %s for %s (circ %d)",
nickname, circ->rend_query, circ->n_circ_id);
- circ->state = CIRCUIT_STATE_BUILDING;
- tor_free(circ->build_state->chosen_exit_name);
- /* no need to strdup, since rend_client_get_random_intro() made
- * it just for us: */
- circ->build_state->chosen_exit_name = nickname;
- memcpy(circ->build_state->chosen_exit_digest, r->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 intro point.");
- circuit_mark_for_close(circ);
+ if (circuit_append_new_hop(circ, nickname, r->identity_digest) < 0)
return -1;
- }
}
}
return 0;
diff --git a/src/or/rendservice.c b/src/or/rendservice.c
index c3561198a..d604cdc6a 100644
--- a/src/or/rendservice.c
+++ b/src/or/rendservice.c
@@ -50,7 +50,7 @@ typedef struct rend_service_t {
int n_intro_circuits_launched; /**< count of intro circuits we have
* established in this period. */
rend_service_descriptor_t *desc;
- int desc_is_dirty;
+ time_t desc_is_dirty;
time_t next_upload_time;
} rend_service_t;
@@ -363,7 +363,7 @@ rend_service_requires_uptime(rend_service_t *service) {
******/
/** Respond to an INTRODUCE2 cell by launching a circuit to the chosen
- * rendezvous points.
+ * rendezvous point.
*/
int
rend_service_introduce(circuit_t *circuit, const char *request, size_t request_len)
@@ -381,6 +381,7 @@ rend_service_introduce(circuit_t *circuit, const char *request, size_t request_l
char hexcookie[9];
int version;
size_t nickname_field_len;
+ int circ_needs_uptime;
base32_encode(serviceid, REND_SERVICE_ID_LEN+1,
circuit->rend_pk_digest,10);
@@ -452,6 +453,7 @@ rend_service_introduce(circuit_t *circuit, const char *request, size_t request_l
/* Okay, now we know that a nickname is at the start of the buffer. */
ptr = rp_nickname+nickname_field_len;
len -= nickname_field_len;
+ len -= rp_nickname - buf; /* also remove header space used by version, if any */
if (len != REND_COOKIE_LEN+DH_KEY_LEN) {
log_fn(LOG_WARN, "Bad length %u for INTRODUCE2 cell.", (int)len);
return -1;
@@ -471,11 +473,16 @@ rend_service_introduce(circuit_t *circuit, const char *request, size_t request_l
goto err;
}
+ circ_needs_uptime = rend_service_requires_uptime(service);
+
+ /* help predict this next time */
+ rep_hist_note_used_hidserv(time(NULL), circ_needs_uptime, 1);
+
/* Launch a circuit to alice's chosen rendezvous point.
*/
for (i=0;i<MAX_REND_FAILURES;i++) {
launched = circuit_launch_by_nickname(CIRCUIT_PURPOSE_S_CONNECT_REND, rp_nickname,
- rend_service_requires_uptime(service), 1);
+ circ_needs_uptime, 1, 1);
if (launched)
break;
}
@@ -540,7 +547,7 @@ rend_service_relaunch_rendezvous(circuit_t *oldcirc)
oldstate->chosen_exit_name);
newcirc = circuit_launch_by_nickname(CIRCUIT_PURPOSE_S_CONNECT_REND,
- oldstate->chosen_exit_name, 0, 1);
+ oldstate->chosen_exit_name, 0, 1, 1);
if (!newcirc) {
log_fn(LOG_WARN,"Couldn't relaunch rendezvous circuit to %s",
oldstate->chosen_exit_name);
@@ -568,8 +575,10 @@ rend_service_launch_establish_intro(rend_service_t *service, const char *nicknam
log_fn(LOG_INFO, "Launching circuit to introduction point %s for service %s",
nickname, service->service_id);
+ rep_hist_note_used_hidserv(time(NULL), 1, 0);
+
++service->n_intro_circuits_launched;
- launched = circuit_launch_by_nickname(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, nickname, 1, 0);
+ launched = circuit_launch_by_nickname(CIRCUIT_PURPOSE_S_ESTABLISH_INTRO, nickname, 1, 0, 1);
if (!launched) {
log_fn(LOG_WARN, "Can't launch circuit to establish introduction at '%s'",
nickname);
@@ -661,7 +670,7 @@ rend_service_intro_established(circuit_t *circuit, const char *request, size_t r
circuit->n_circ_id);
goto err;
}
- service->desc_is_dirty = 1;
+ service->desc_is_dirty = time(NULL);
circuit->purpose = CIRCUIT_PURPOSE_S_INTRO;
return 0;
@@ -849,7 +858,8 @@ void rend_services_introduce(void) {
intro, service->service_id);
tor_free(intro);
smartlist_del(service->intro_nodes,j--);
- changed = service->desc_is_dirty = 1;
+ changed = 1;
+ service->desc_is_dirty = now;
}
smartlist_add(intro_routers, router);
}
@@ -931,9 +941,9 @@ rend_consider_services_upload(time_t now) {
}
if (service->next_upload_time < now ||
(service->desc_is_dirty &&
- service->next_upload_time < now-5)) {
+ service->desc_is_dirty < now-5)) {
/* if it's time, or if the directory servers have a wrong service
- * descriptor and this has been the case for 5 seconds, upload a
+ * descriptor and ours has been stable for 5 seconds, upload a
* new one. */
upload_service_descriptor(service);
service->next_upload_time = now + rendpostperiod;
diff --git a/src/or/rephist.c b/src/or/rephist.c
index 568988f26..453703280 100644
--- a/src/or/rephist.c
+++ b/src/or/rephist.c
@@ -666,7 +666,7 @@ void rep_hist_note_used_port(uint16_t port, time_t now) {
add_predicted_port(port, now);
}
-#define PREDICTED_PORTS_RELEVANCE_TIME (6*3600) /* 6 hours */
+#define PREDICTED_CIRCS_RELEVANCE_TIME (3600) /* 1 hour */
/** Return a pointer to the list of port numbers that
* are likely to be asked for in the near future.
@@ -684,7 +684,7 @@ smartlist_t *rep_hist_get_predicted_ports(time_t now) {
/* clean out obsolete entries */
for (i = 0; i < smartlist_len(predicted_ports_list); ++i) {
tmp_time = smartlist_get(predicted_ports_times, i);
- if (*tmp_time + PREDICTED_PORTS_RELEVANCE_TIME < now) {
+ if (*tmp_time + PREDICTED_CIRCS_RELEVANCE_TIME < now) {
tmp_port = smartlist_get(predicted_ports_list, i);
log_fn(LOG_DEBUG, "Expiring predicted port %d", *tmp_port);
smartlist_del(predicted_ports_list, i);
@@ -697,3 +697,36 @@ smartlist_t *rep_hist_get_predicted_ports(time_t now) {
return predicted_ports_list;
}
+/** The last time at which we needed an internal circ. */
+static time_t predicted_hidserv_time = 0;
+/** The last time we needed an internal circ with good uptime. */
+static time_t predicted_hidserv_uptime_time = 0;
+/** The last time we needed an internal circ with good capacity. */
+static time_t predicted_hidserv_capacity_time = 0;
+
+/** Remember that we used an internal circ at time <b>now</b>. */
+void rep_hist_note_used_hidserv(time_t now, int need_uptime, int need_capacity) {
+ predicted_hidserv_time = now;
+ if (need_uptime)
+ predicted_hidserv_uptime_time = now;
+ if (need_capacity)
+ predicted_hidserv_capacity_time = now;
+}
+
+/** Return 1 if we've used an internal circ recently; else return 0. */
+int rep_hist_get_predicted_hidserv(time_t now, int *need_uptime, int *need_capacity) {
+ if (!predicted_hidserv_time) /* initialize it */
+ predicted_hidserv_time = now;
+ if (predicted_hidserv_time + PREDICTED_CIRCS_RELEVANCE_TIME < now)
+ return 0; /* too long ago */
+ if (predicted_hidserv_uptime_time + PREDICTED_CIRCS_RELEVANCE_TIME < now)
+ *need_uptime = 1;
+ if (predicted_hidserv_capacity_time + PREDICTED_CIRCS_RELEVANCE_TIME < now)
+ *need_capacity = 1;
+ return 1;
+}
+
+/* not used yet */
+void rep_hist_note_used_resolve(time_t now) { }
+int rep_hist_get_predicted_resolve(time_t now) { return 0; }
+