Merge remote-tracking branch 'rransom-tor/bug3460-v4'

Conflicts:
	src/or/rendservice.c
This commit is contained in:
Nick Mathewson 2011-11-29 20:56:39 -05:00
commit 628b735fe3
7 changed files with 367 additions and 85 deletions

11
changes/bug3460 Normal file
View File

@ -0,0 +1,11 @@
o Major bugfixes:
- Ignore the timestamps of INTRODUCE2 cells received by a hidden
service. Previously, hidden services would check that the
timestamp was within 30 minutes of their system clock, so that
services could keep only INTRODUCE2 cells they had received in
the last hour in their replay-detection cache. Bugfix on
0.2.1.6-alpha, when the v3 intro-point protocol (the first one
which sent a timestamp field in the INTRODUCE2 cell) was
introduced; fixes bug 3460.

View File

@ -0,0 +1,5 @@
o Minor features:
- Expire old or over-used hidden service introduction points.
Required by fix for bug 3460.

View File

@ -0,0 +1,7 @@
o Minor features:
- Move the replay-detection cache for the RSA-encrypted parts of
INTRODUCE2 cells to the introduction point data structures.
Previously, we would use one replay-detection cache per hidden
service. Required by fix for bug 3460.

View File

@ -0,0 +1,9 @@
o Minor features:
- Reduce the lifetime of elements of hidden services'
Diffie-Hellman public key replay-detection cache from 60 minutes
to 5 minutes. This replay-detection cache is now used only to
detect multiple INTRODUCE2 cells specifying the same rendezvous
point, so we don't launch multiple simultaneous attempts to
connect to it.

View File

@ -789,10 +789,10 @@ typedef struct rend_data_t {
char rend_cookie[REND_COOKIE_LEN]; char rend_cookie[REND_COOKIE_LEN];
} rend_data_t; } rend_data_t;
/** Time interval for tracking possible replays of INTRODUCE2 cells. /** Time interval for tracking replays of DH public keys received in
* Incoming cells with timestamps half of this interval in the past or * INTRODUCE2 cells. Used only to avoid launching multiple
* future are dropped immediately. */ * simultaneous attempts to connect to the same rendezvous point. */
#define REND_REPLAY_TIME_INTERVAL (60 * 60) #define REND_REPLAY_TIME_INTERVAL (5 * 60)
/** Used to indicate which way a cell is going on a circuit. */ /** Used to indicate which way a cell is going on a circuit. */
typedef enum { typedef enum {
@ -4046,6 +4046,26 @@ typedef struct rend_encoded_v2_service_descriptor_t {
* introduction point. See also rend_intro_point_t.unreachable_count. */ * introduction point. See also rend_intro_point_t.unreachable_count. */
#define MAX_INTRO_POINT_REACHABILITY_FAILURES 5 #define MAX_INTRO_POINT_REACHABILITY_FAILURES 5
/** The maximum number of distinct INTRODUCE2 cells which a hidden
* service's introduction point will receive before it begins to
* expire.
*
* XXX023 Is this number at all sane? */
#define INTRO_POINT_LIFETIME_INTRODUCTIONS 16384
/** The minimum number of seconds that an introduction point will last
* before expiring due to old age. (If it receives
* INTRO_POINT_LIFETIME_INTRODUCTIONS INTRODUCE2 cells, it may expire
* sooner.)
*
* XXX023 Should this be configurable? */
#define INTRO_POINT_LIFETIME_MIN_SECONDS 18*60*60
/** The maximum number of seconds that an introduction point will last
* before expiring due to old age.
*
* XXX023 Should this be configurable? */
#define INTRO_POINT_LIFETIME_MAX_SECONDS 24*60*60
/** Introduction point information. Used both in rend_service_t (on /** Introduction point information. Used both in rend_service_t (on
* the service side) and in rend_service_descriptor_t (on both the * the service side) and in rend_service_descriptor_t (on both the
* client and service side). */ * client and service side). */
@ -4065,6 +4085,37 @@ typedef struct rend_intro_point_t {
* circuit to this intro point for some reason other than our * circuit to this intro point for some reason other than our
* circuit-build timeout. See also MAX_INTRO_POINT_REACHABILITY_FAILURES. */ * circuit-build timeout. See also MAX_INTRO_POINT_REACHABILITY_FAILURES. */
unsigned int unreachable_count : 3; unsigned int unreachable_count : 3;
/** (Service side only) Flag indicating that this intro point was
* included in the last HS descriptor we generated. */
unsigned int listed_in_last_desc : 1;
/** (Service side only) A digestmap recording the INTRODUCE2 cells
* this intro point's circuit has received. Each key is the digest
* of the RSA-encrypted part of a received INTRODUCE2 cell; each
* value is a pointer to the time_t at which the cell was received.
* This digestmap is used to prevent replay attacks. */
digestmap_t *accepted_intro_rsa_parts;
/** (Service side only) The time at which this intro point was first
* published, or -1 if this intro point has not yet been
* published. */
time_t time_published;
/** (Service side only) The time at which this intro point should
* (start to) expire, or -1 if we haven't decided when this intro
* point should expire. */
time_t time_to_expire;
/** (Service side only) The time at which we decided that this intro
* point should start expiring, or -1 if this intro point is not yet
* expiring.
*
* This field also serves as a flag to indicate that we have decided
* to expire this intro point, in case intro_point_should_expire_now
* flaps (perhaps due to a clock jump; perhaps due to other
* weirdness, or even a (present or future) bug). */
time_t time_expiring;
} rend_intro_point_t; } rend_intro_point_t;
/** Information used to connect to a hidden service. Used on both the /** Information used to connect to a hidden service. Used on both the

View File

@ -440,6 +440,11 @@ rend_intro_point_free(rend_intro_point_t *intro)
extend_info_free(intro->extend_info); extend_info_free(intro->extend_info);
crypto_free_pk_env(intro->intro_key); crypto_free_pk_env(intro->intro_key);
if (intro->accepted_intro_rsa_parts != NULL) {
digestmap_free(intro->accepted_intro_rsa_parts, _tor_free);
}
tor_free(intro); tor_free(intro);
} }

View File

@ -26,6 +26,10 @@
static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro, static origin_circuit_t *find_intro_circuit(rend_intro_point_t *intro,
const char *pk_digest); const char *pk_digest);
static rend_intro_point_t *find_intro_point(origin_circuit_t *circ);
static int intro_point_should_expire_now(rend_intro_point_t *intro,
time_t now);
/** Represents the mapping from a virtual port of a rendezvous service to /** Represents the mapping from a virtual port of a rendezvous service to
* a real port on some IP. * a real port on some IP.
@ -36,8 +40,10 @@ typedef struct rend_service_port_config_t {
tor_addr_t real_addr; tor_addr_t real_addr;
} rend_service_port_config_t; } rend_service_port_config_t;
/** Try to maintain this many intro points per service if possible. */ /** Try to maintain this many intro points per service by default. */
#define NUM_INTRO_POINTS 3 #define NUM_INTRO_POINTS_DEFAULT 3
/** Maintain no more than this many intro points per hidden service. */
#define NUM_INTRO_POINTS_MAX 10
/** If we can't build our intro circuits, don't retry for this long. */ /** If we can't build our intro circuits, don't retry for this long. */
#define INTRO_CIRC_RETRY_PERIOD (60*5) #define INTRO_CIRC_RETRY_PERIOD (60*5)
@ -51,6 +57,10 @@ typedef struct rend_service_port_config_t {
* rendezvous point before giving up? */ * rendezvous point before giving up? */
#define MAX_REND_TIMEOUT 30 #define MAX_REND_TIMEOUT 30
/** How many seconds should we wait for new HS descriptors to reach
* our clients before we close an expiring intro point? */
#define INTRO_POINT_EXPIRATION_GRACE_PERIOD 5*60
/** Represents a single hidden service running at this OP. */ /** Represents a single hidden service running at this OP. */
typedef struct rend_service_t { typedef struct rend_service_t {
/* Fields specified in config file */ /* Fields specified in config file */
@ -72,17 +82,24 @@ typedef struct rend_service_t {
* introduction points. */ * introduction points. */
int n_intro_circuits_launched; /**< Count of intro circuits we have int n_intro_circuits_launched; /**< Count of intro circuits we have
* established in this period. */ * established in this period. */
unsigned int n_intro_points_wanted; /**< Number of intro points this
* service wants to have open. */
rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */ rend_service_descriptor_t *desc; /**< Current hidden service descriptor. */
time_t desc_is_dirty; /**< Time at which changes to the hidden service time_t desc_is_dirty; /**< Time at which changes to the hidden service
* descriptor content occurred, or 0 if it's * descriptor content occurred, or 0 if it's
* up-to-date. */ * up-to-date. */
time_t next_upload_time; /**< Scheduled next hidden service descriptor time_t next_upload_time; /**< Scheduled next hidden service descriptor
* upload time. */ * upload time. */
/** Map from digests of Diffie-Hellman values INTRODUCE2 to time_t of when /** Map from digests of Diffie-Hellman values INTRODUCE2 to time_t
* they were received; used to prevent replays. */ * of when they were received. Clients may send INTRODUCE1 cells
digestmap_t *accepted_intros; * for the same rendezvous point through two or more different
/** Time at which we last removed expired values from accepted_intros. */ * introduction points; when they do, this digestmap keeps us from
time_t last_cleaned_accepted_intros; * launching multiple simultaneous attempts to connect to the same
* rend point. */
digestmap_t *accepted_intro_dh_parts;
/** Time at which we last removed expired values from
* accepted_intro_dh_parts. */
time_t last_cleaned_accepted_intro_dh_parts;
} rend_service_t; } rend_service_t;
/** A list of rend_service_t's for services run on this OP. /** A list of rend_service_t's for services run on this OP.
@ -142,7 +159,7 @@ rend_service_free(rend_service_t *service)
rend_authorized_client_free(c);); rend_authorized_client_free(c););
smartlist_free(service->clients); smartlist_free(service->clients);
} }
digestmap_free(service->accepted_intros, _tor_free); digestmap_free(service->accepted_intro_dh_parts, _tor_free);
tor_free(service); tor_free(service);
} }
@ -319,6 +336,7 @@ rend_config_services(const or_options_t *options, int validate_only)
service->directory = tor_strdup(line->value); service->directory = tor_strdup(line->value);
service->ports = smartlist_create(); service->ports = smartlist_create();
service->intro_period_started = time(NULL); service->intro_period_started = time(NULL);
service->n_intro_points_wanted = NUM_INTRO_POINTS_DEFAULT;
continue; continue;
} }
if (!service) { if (!service) {
@ -542,16 +560,38 @@ rend_service_update_descriptor(rend_service_t *service)
for (i = 0; i < smartlist_len(service->intro_nodes); ++i) { for (i = 0; i < smartlist_len(service->intro_nodes); ++i) {
rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i); rend_intro_point_t *intro_svc = smartlist_get(service->intro_nodes, i);
rend_intro_point_t *intro_desc; rend_intro_point_t *intro_desc;
circ = find_intro_circuit(intro_svc, service->pk_digest);
if (!circ || circ->_base.purpose != CIRCUIT_PURPOSE_S_INTRO)
continue;
/* We have an entirely established intro circuit. */ /* This intro point won't be listed in the descriptor... */
intro_svc->listed_in_last_desc = 0;
if (intro_svc->time_expiring != -1) {
/* This intro point is expiring. Don't list it. */
continue;
}
circ = find_intro_circuit(intro_svc, service->pk_digest);
if (!circ || circ->_base.purpose != CIRCUIT_PURPOSE_S_INTRO) {
/* This intro point's circuit isn't finished yet. Don't list it. */
continue;
}
/* ...unless this intro point is listed in the descriptor. */
intro_svc->listed_in_last_desc = 1;
/* We have an entirely established intro circuit. Publish it in
* our descriptor. */
intro_desc = tor_malloc_zero(sizeof(rend_intro_point_t)); intro_desc = tor_malloc_zero(sizeof(rend_intro_point_t));
intro_desc->extend_info = extend_info_dup(intro_svc->extend_info); intro_desc->extend_info = extend_info_dup(intro_svc->extend_info);
if (intro_svc->intro_key) if (intro_svc->intro_key)
intro_desc->intro_key = crypto_pk_dup_key(intro_svc->intro_key); intro_desc->intro_key = crypto_pk_dup_key(intro_svc->intro_key);
smartlist_add(d->intro_nodes, intro_desc); smartlist_add(d->intro_nodes, intro_desc);
if (intro_svc->time_published == -1) {
/* We are publishing this intro point in a descriptor for the
* first time -- note the current time in the service's copy of
* the intro point. */
intro_svc->time_published = time(NULL);
}
} }
} }
@ -857,15 +897,16 @@ rend_check_authorization(rend_service_t *service,
/** Remove elements from <b>service</b>'s replay cache that are old enough to /** Remove elements from <b>service</b>'s replay cache that are old enough to
* be noticed by timestamp checking. */ * be noticed by timestamp checking. */
static void static void
clean_accepted_intros(rend_service_t *service, time_t now) clean_accepted_intro_dh_parts(rend_service_t *service, time_t now)
{ {
const time_t cutoff = now - REND_REPLAY_TIME_INTERVAL; const time_t cutoff = now - REND_REPLAY_TIME_INTERVAL;
service->last_cleaned_accepted_intros = now; service->last_cleaned_accepted_intro_dh_parts = now;
if (!service->accepted_intros) if (!service->accepted_intro_dh_parts)
return; return;
DIGESTMAP_FOREACH_MODIFY(service->accepted_intros, digest, time_t *, t) { DIGESTMAP_FOREACH_MODIFY(service->accepted_intro_dh_parts, digest,
time_t *, t) {
if (*t < cutoff) { if (*t < cutoff) {
tor_free(t); tor_free(t);
MAP_DEL_CURRENT(digest); MAP_DEL_CURRENT(digest);
@ -890,6 +931,7 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
char buf[RELAY_PAYLOAD_SIZE]; char buf[RELAY_PAYLOAD_SIZE];
char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */ char keys[DIGEST_LEN+CPATH_KEY_MATERIAL_LEN]; /* Holds KH, Df, Db, Kf, Kb */
rend_service_t *service; rend_service_t *service;
rend_intro_point_t *intro_point;
int r, i, v3_shift = 0; int r, i, v3_shift = 0;
size_t len, keylen; size_t len, keylen;
crypto_dh_env_t *dh = NULL; crypto_dh_env_t *dh = NULL;
@ -937,7 +979,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
service = rend_service_get_by_pk_digest( service = rend_service_get_by_pk_digest(
circuit->rend_data->rend_pk_digest); circuit->rend_data->rend_pk_digest);
if (!service) { if (!service) {
log_warn(LD_REND, "Got an INTRODUCE2 cell for an unrecognized service %s.", log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro "
"circ for an unrecognized service %s.",
escaped(serviceid)); escaped(serviceid));
return -1; return -1;
} }
@ -962,17 +1005,26 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
return -1; return -1;
} }
if (!service->accepted_intros) intro_point = find_intro_point(circuit);
service->accepted_intros = digestmap_new(); if (intro_point == NULL) {
log_warn(LD_BUG, "Internal error: Got an INTRODUCE2 cell on an intro circ "
"(for service %s) with no corresponding rend_intro_point_t.",
escaped(serviceid));
return -1;
}
if (!service->accepted_intro_dh_parts)
service->accepted_intro_dh_parts = digestmap_new();
if (!intro_point->accepted_intro_rsa_parts)
intro_point->accepted_intro_rsa_parts = digestmap_new();
{ {
char pkpart_digest[DIGEST_LEN]; char pkpart_digest[DIGEST_LEN];
/* Check for replay of PK-encrypted portion. It is slightly naughty to /* Check for replay of PK-encrypted portion. */
use the same digestmap to check for this and for g^x replays, but
collisions are tremendously unlikely.
*/
crypto_digest(pkpart_digest, (char*)request+DIGEST_LEN, keylen); crypto_digest(pkpart_digest, (char*)request+DIGEST_LEN, keylen);
access_time = digestmap_get(service->accepted_intros, pkpart_digest); access_time = digestmap_get(intro_point->accepted_intro_rsa_parts,
pkpart_digest);
if (access_time != NULL) { if (access_time != NULL) {
log_warn(LD_REND, "Possible replay detected! We received an " log_warn(LD_REND, "Possible replay detected! We received an "
"INTRODUCE2 cell with same PK-encrypted part %d seconds ago. " "INTRODUCE2 cell with same PK-encrypted part %d seconds ago. "
@ -981,7 +1033,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
} }
access_time = tor_malloc(sizeof(time_t)); access_time = tor_malloc(sizeof(time_t));
*access_time = now; *access_time = now;
digestmap_set(service->accepted_intros, pkpart_digest, access_time); digestmap_set(intro_point->accepted_intro_rsa_parts,
pkpart_digest, access_time);
} }
/* Next N bytes is encrypted with service key */ /* Next N bytes is encrypted with service key */
@ -997,7 +1050,6 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
len = r; len = r;
if (*buf == 3) { if (*buf == 3) {
/* Version 3 INTRODUCE2 cell. */ /* Version 3 INTRODUCE2 cell. */
time_t ts = 0;
v3_shift = 1; v3_shift = 1;
auth_type = buf[1]; auth_type = buf[1];
switch (auth_type) { switch (auth_type) {
@ -1019,17 +1071,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
log_info(LD_REND, "Unknown authorization type '%d'", auth_type); log_info(LD_REND, "Unknown authorization type '%d'", auth_type);
} }
/* Check timestamp. */ /* Skip the timestamp field. We no longer use it. */
ts = ntohl(get_uint32(buf+1+v3_shift));
v3_shift += 4; v3_shift += 4;
if ((now - ts) < -1 * REND_REPLAY_TIME_INTERVAL / 2 ||
(now - ts) > REND_REPLAY_TIME_INTERVAL / 2) {
/* This is far more likely to mean that a client's clock is
* skewed than that a replay attack is in progress. */
log_info(LD_REND, "INTRODUCE2 cell is too %s. Discarding.",
(now - ts) < 0 ? "old" : "new");
return -1;
}
} }
if (*buf == 2 || *buf == 3) { if (*buf == 2 || *buf == 3) {
/* Version 2 INTRODUCE2 cell. */ /* Version 2 INTRODUCE2 cell. */
@ -1128,7 +1171,8 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
/* Check whether there is a past request with the same Diffie-Hellman, /* Check whether there is a past request with the same Diffie-Hellman,
* part 1. */ * part 1. */
access_time = digestmap_get(service->accepted_intros, diffie_hellman_hash); access_time = digestmap_get(service->accepted_intro_dh_parts,
diffie_hellman_hash);
if (access_time != NULL) { if (access_time != NULL) {
/* A Tor client will send a new INTRODUCE1 cell with the same rend /* A Tor client will send a new INTRODUCE1 cell with the same rend
* cookie and DH public key as its previous one if its intro circ * cookie and DH public key as its previous one if its intro circ
@ -1150,9 +1194,11 @@ rend_service_introduce(origin_circuit_t *circuit, const uint8_t *request,
* one hour. */ * one hour. */
access_time = tor_malloc(sizeof(time_t)); access_time = tor_malloc(sizeof(time_t));
*access_time = now; *access_time = now;
digestmap_set(service->accepted_intros, diffie_hellman_hash, access_time); digestmap_set(service->accepted_intro_dh_parts,
if (service->last_cleaned_accepted_intros + REND_REPLAY_TIME_INTERVAL < now) diffie_hellman_hash, access_time);
clean_accepted_intros(service, now); if (service->last_cleaned_accepted_intro_dh_parts + REND_REPLAY_TIME_INTERVAL
< now)
clean_accepted_intro_dh_parts(service, now);
/* If the service performs client authorization, check included auth data. */ /* If the service performs client authorization, check included auth data. */
if (service->clients) { if (service->clients) {
@ -1412,7 +1458,8 @@ rend_service_intro_has_opened(origin_circuit_t *circuit)
/* If we already have enough introduction circuits for this service, /* If we already have enough introduction circuits for this service,
* redefine this one as a general circuit or close it, depending. */ * redefine this one as a general circuit or close it, depending. */
if (count_established_intro_points(serviceid) > NUM_INTRO_POINTS) { if (count_established_intro_points(serviceid) >
(int)service->n_intro_points_wanted) { /* XXX023 remove cast */
const or_options_t *options = get_options(); const or_options_t *options = get_options();
if (options->ExcludeNodes) { if (options->ExcludeNodes) {
/* XXXX in some future version, we can test whether the transition is /* XXXX in some future version, we can test whether the transition is
@ -1652,6 +1699,35 @@ find_intro_circuit(rend_intro_point_t *intro, const char *pk_digest)
return NULL; return NULL;
} }
/** Return a pointer to the rend_intro_point_t corresponding to the
* service-side introduction circuit <b>circ</b>. */
static rend_intro_point_t *
find_intro_point(origin_circuit_t *circ)
{
const char *serviceid;
rend_service_t *service = NULL;
tor_assert(TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_ESTABLISH_INTRO ||
TO_CIRCUIT(circ)->purpose == CIRCUIT_PURPOSE_S_INTRO);
tor_assert(circ->rend_data);
serviceid = circ->rend_data->onion_address;
SMARTLIST_FOREACH(rend_service_list, rend_service_t *, s,
if (tor_memeq(s->service_id, serviceid, REND_SERVICE_ID_LEN_BASE32)) {
service = s;
break;
});
if (service == NULL) return NULL;
SMARTLIST_FOREACH(service->intro_nodes, rend_intro_point_t *, intro_point,
if (crypto_pk_cmp_keys(intro_point->intro_key, circ->intro_key) == 0) {
return intro_point;
});
return NULL;
}
/** Determine the responsible hidden service directories for the /** Determine the responsible hidden service directories for the
* rend_encoded_v2_service_descriptor_t's in <b>descs</b> and upload them; * rend_encoded_v2_service_descriptor_t's in <b>descs</b> and upload them;
* <b>service_id</b> and <b>seconds_valid</b> are only passed for logging * <b>service_id</b> and <b>seconds_valid</b> are only passed for logging
@ -1855,6 +1931,52 @@ upload_service_descriptor(rend_service_t *service)
service->desc_is_dirty = 0; service->desc_is_dirty = 0;
} }
/** Return non-zero iff <b>intro</b> should 'expire' now (i.e. we
* should stop publishing it in new descriptors and eventually close
* it). */
static int
intro_point_should_expire_now(rend_intro_point_t *intro,
time_t now)
{
tor_assert(intro != NULL);
if (intro->time_published == -1) {
/* Don't expire an intro point if we haven't even published it yet. */
return 0;
}
if (intro->time_expiring != -1) {
/* We've already started expiring this intro point. *Don't* let
* this function's result 'flap'. */
return 1;
}
if (digestmap_size(intro->accepted_intro_rsa_parts) >=
INTRO_POINT_LIFETIME_INTRODUCTIONS) {
/* This intro point has been used too many times. Expire it now. */
return 1;
}
if (intro->time_to_expire == -1) {
/* This intro point has been published, but we haven't picked an
* expiration time for it. Pick one now. */
int intro_point_lifetime_seconds =
INTRO_POINT_LIFETIME_MIN_SECONDS +
crypto_rand_int(INTRO_POINT_LIFETIME_MAX_SECONDS -
INTRO_POINT_LIFETIME_MIN_SECONDS);
/* Start the expiration timer now, rather than when the intro
* point was first published. There shouldn't be much of a time
* difference. */
intro->time_to_expire = now + intro_point_lifetime_seconds;
return 0;
}
/* This intro point has a time to expire set already. Use it. */
return (now >= intro->time_to_expire);
}
/** For every service, check how many intro points it currently has, and: /** For every service, check how many intro points it currently has, and:
* - Pick new intro points as necessary. * - Pick new intro points as necessary.
* - Launch circuits to any new intro points. * - Launch circuits to any new intro points.
@ -1866,7 +1988,9 @@ rend_services_introduce(void)
const node_t *node; const node_t *node;
rend_service_t *service; rend_service_t *service;
rend_intro_point_t *intro; rend_intro_point_t *intro;
int changed, prev_intro_nodes; int intro_point_set_changed, prev_intro_nodes;
unsigned int n_intro_points_unexpired;
unsigned int n_intro_points_to_open;
smartlist_t *intro_nodes; smartlist_t *intro_nodes;
time_t now; time_t now;
const or_options_t *options = get_options(); const or_options_t *options = get_options();
@ -1879,7 +2003,16 @@ rend_services_introduce(void)
service = smartlist_get(rend_service_list, i); service = smartlist_get(rend_service_list, i);
tor_assert(service); tor_assert(service);
changed = 0;
/* intro_point_set_changed becomes non-zero iff the set of intro
* points to be published in service's descriptor has changed. */
intro_point_set_changed = 0;
/* n_intro_points_unexpired collects the number of non-expiring
* intro points we have, so that we know how many new intro
* circuits we need to launch for this service. */
n_intro_points_unexpired = 0;
if (now > service->intro_period_started+INTRO_CIRC_RETRY_PERIOD) { if (now > service->intro_period_started+INTRO_CIRC_RETRY_PERIOD) {
/* One period has elapsed; we can try building circuits again. */ /* One period has elapsed; we can try building circuits again. */
service->intro_period_started = now; service->intro_period_started = now;
@ -1893,59 +2026,115 @@ rend_services_introduce(void)
/* Find out which introduction points we have in progress for this /* Find out which introduction points we have in progress for this
service. */ service. */
for (j=0; j < smartlist_len(service->intro_nodes); ++j) { SMARTLIST_FOREACH_BEGIN(service->intro_nodes, rend_intro_point_t *, intro){
intro = smartlist_get(service->intro_nodes, j); origin_circuit_t *intro_circ =
find_intro_circuit(intro, service->pk_digest);
if (intro->time_expiring + INTRO_POINT_EXPIRATION_GRACE_PERIOD > now) {
/* This intro point has completely expired. Remove it, and
* mark the circuit for close if it's still alive. */
if (intro_circ != NULL) {
circuit_mark_for_close(TO_CIRCUIT(intro_circ),
END_CIRC_REASON_FINISHED);
}
rend_intro_point_free(intro);
intro = NULL; /* SMARTLIST_DEL_CURRENT takes a name, not a value. */
SMARTLIST_DEL_CURRENT(service->intro_nodes, intro);
/* We don't need to set intro_point_set_changed here, because
* this intro point wouldn't have been published in a current
* descriptor anyway. */
continue;
}
node = node_get_by_id(intro->extend_info->identity_digest); node = node_get_by_id(intro->extend_info->identity_digest);
if (!node || !find_intro_circuit(intro, service->pk_digest)) { if (!node || !intro_circ) {
log_info(LD_REND,"Giving up on %s as intro point for %s.", int removing_this_intro_point_changes_the_intro_point_set = 1;
log_info(LD_REND, "Giving up on %s as intro point for %s"
" (circuit disappeared).",
safe_str_client(extend_info_describe(intro->extend_info)), safe_str_client(extend_info_describe(intro->extend_info)),
safe_str_client(service->service_id)); safe_str_client(service->service_id));
if (service->desc) { if (intro->time_expiring != -1) {
SMARTLIST_FOREACH(service->desc->intro_nodes, rend_intro_point_t *, log_info(LD_REND, "We were already expiring the intro point; "
dintro, { "no need to mark the HS descriptor as dirty over this.");
if (tor_memeq(dintro->extend_info->identity_digest, removing_this_intro_point_changes_the_intro_point_set = 0;
intro->extend_info->identity_digest, DIGEST_LEN)) { } else if (intro->listed_in_last_desc) {
log_info(LD_REND, "The intro point we are giving up on was " log_info(LD_REND, "The intro point we are giving up on was "
"included in the last published descriptor. " "included in the last published descriptor. "
"Marking current descriptor as dirty."); "Marking current descriptor as dirty.");
service->desc_is_dirty = now; service->desc_is_dirty = now;
} }
});
}
rend_intro_point_free(intro); rend_intro_point_free(intro);
smartlist_del(service->intro_nodes,j--); intro = NULL; /* SMARTLIST_DEL_CURRENT takes a name, not a value. */
changed = 1; SMARTLIST_DEL_CURRENT(service->intro_nodes, intro);
} if (removing_this_intro_point_changes_the_intro_point_set)
if (node) intro_point_set_changed = 1;
smartlist_add(intro_nodes, (void*)node);
} }
/* We have enough intro points, and the intro points we thought we had were if (intro != NULL && intro_point_should_expire_now(intro, now)) {
* all connected. log_info(LD_REND, "Expiring %s as intro point for %s.",
*/ safe_str_client(extend_info_describe(intro->extend_info)),
if (!changed && smartlist_len(service->intro_nodes) >= NUM_INTRO_POINTS) { safe_str_client(service->service_id));
/* We have all our intro points! Start a fresh period and reset the
* circuit count. */ /* The polite (and generally Right) way to expire an intro
* point is to establish a new one to replace it, publish a
* new descriptor that doesn't list any expiring intro points,
* and *then*, once our upload attempts for the new descriptor
* have ended (whether in success or failure), close the
* expiring intro points.
*
* Unfortunately, we can't find out when the new descriptor
* has actually been uploaded, so we'll have to settle for a
* five-minute timer. Start it. XXX023 This sucks. */
intro->time_expiring = now;
intro_point_set_changed = 1;
}
if (intro != NULL && intro->time_expiring == -1)
++n_intro_points_unexpired;
if (node)
smartlist_add(intro_nodes, (void*)node);
} SMARTLIST_FOREACH_END(intro);
if (!intro_point_set_changed &&
(n_intro_points_unexpired >= service->n_intro_points_wanted)) {
/* We have enough intro circuits in progress, and none of our
* intro circuits have died since the last call to
* rend_services_introduce! Start a fresh period and reset the
* circuit count.
*
* XXXX WTF? */
service->intro_period_started = now; service->intro_period_started = now;
service->n_intro_circuits_launched = 0; service->n_intro_circuits_launched = 0;
continue; continue;
} }
/* Remember how many introduction circuits we started with. */ /* Remember how many introduction circuits we started with.
*
* prev_intro_nodes serves a different purpose than
* n_intro_points_unexpired -- this variable tells us where our
* previously-created intro points end and our new ones begin in
* the intro-point list, so we don't have to launch the circuits
* at the same time as we create the intro points they correspond
* to. XXXX This is daft. */
prev_intro_nodes = smartlist_len(service->intro_nodes); prev_intro_nodes = smartlist_len(service->intro_nodes);
/* We have enough directory information to start establishing our /* We have enough directory information to start establishing our
* intro points. We want to end up with three intro points, but if * intro points. We want to end up with n_intro_points_wanted
* we're just starting, we launch five and pick the first three that * intro points, but if we're just starting, we launch two extra
* complete. * circuits and use the first n_intro_points_wanted that complete.
* *
* The ones after the first three will be converted to 'general' * The ones after the first three will be converted to 'general'
* internal circuits in rend_service_intro_has_opened(), and then * internal circuits in rend_service_intro_has_opened(), and then
* we'll drop them from the list of intro points next time we * we'll drop them from the list of intro points next time we
* go through the above "find out which introduction points we have * go through the above "find out which introduction points we have
* in progress" loop. */ * in progress" loop. */
#define NUM_INTRO_POINTS_INIT (NUM_INTRO_POINTS + 2) n_intro_points_to_open = (service->n_intro_points_wanted +
for (j=prev_intro_nodes; j < (prev_intro_nodes == 0 ? (prev_intro_nodes == 0 ? 2 : 0));
NUM_INTRO_POINTS_INIT : NUM_INTRO_POINTS); ++j) { for (j = (int)n_intro_points_unexpired;
j < (int)n_intro_points_to_open;
++j) { /* XXXX remove casts */
router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC; router_crn_flags_t flags = CRN_NEED_UPTIME|CRN_NEED_DESC;
if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION) if (get_options()->_AllowInvalid & ALLOW_INVALID_INTRODUCTION)
flags |= CRN_ALLOW_INVALID; flags |= CRN_ALLOW_INVALID;
@ -1953,16 +2142,21 @@ rend_services_introduce(void)
options->ExcludeNodes, flags); options->ExcludeNodes, flags);
if (!node) { if (!node) {
log_warn(LD_REND, log_warn(LD_REND,
"Could only establish %d introduction points for %s.", "Could only establish %d introduction points for %s; "
smartlist_len(service->intro_nodes), service->service_id); "wanted %u.",
smartlist_len(service->intro_nodes), service->service_id,
n_intro_points_to_open);
break; break;
} }
changed = 1; intro_point_set_changed = 1;
smartlist_add(intro_nodes, (void*)node); smartlist_add(intro_nodes, (void*)node);
intro = tor_malloc_zero(sizeof(rend_intro_point_t)); intro = tor_malloc_zero(sizeof(rend_intro_point_t));
intro->extend_info = extend_info_from_node(node); intro->extend_info = extend_info_from_node(node);
intro->intro_key = crypto_new_pk_env(); intro->intro_key = crypto_new_pk_env();
tor_assert(!crypto_pk_generate_key(intro->intro_key)); tor_assert(!crypto_pk_generate_key(intro->intro_key));
intro->time_published = -1;
intro->time_to_expire = -1;
intro->time_expiring = -1;
smartlist_add(service->intro_nodes, intro); smartlist_add(service->intro_nodes, intro);
log_info(LD_REND, "Picked router %s as an intro point for %s.", log_info(LD_REND, "Picked router %s as an intro point for %s.",
safe_str_client(node_describe(node)), safe_str_client(node_describe(node)),
@ -1970,7 +2164,7 @@ rend_services_introduce(void)
} }
/* If there's no need to launch new circuits, stop here. */ /* If there's no need to launch new circuits, stop here. */
if (!changed) if (!intro_point_set_changed)
continue; continue;
/* Establish new introduction points. */ /* Establish new introduction points. */