More work towards making bridge users able to connect via bridges:

- demand options->Bridges and options->TunnelDirConns if 
    options->UseBridges is set.
  - after directory fetches, accept descriptors that aren't referenced by
    our networkstatuses, *if* they're for a configured bridge.
  - delay directory fetching until we have at least one bridge descriptor.
  - learn how to build a one-hop circuit when we have neither routerinfo
    nor routerstatus for our destination.
  - teach directory connections how to pick a bridge as the destination
    directory when doing non-anonymous fetches.
  - tolerate directory commands for which the dir_port is 0.
  - remember descriptors when the requested_resource was "authority", 
    rather than just ignoring them.
  - put bridges on our entry_guards list once we have a descriptor for them.
    When UseBridges is set, only pick entry guards that are bridges. Else
    vice versa.


svn:r10571
This commit is contained in:
Roger Dingledine
2007-06-12 09:17:23 +00:00
parent 622dd4927e
commit af658b7828
9 changed files with 235 additions and 69 deletions
+118 -35
View File
@@ -55,7 +55,6 @@ static int onion_extend_cpath(origin_circuit_t *circ);
static int count_acceptable_routers(smartlist_t *routers);
static int onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice);
static routerinfo_t *choose_random_entry(cpath_build_state_t *state);
static void entry_guards_changed(void);
/** Iterate over values of circ_id, starting from conn-\>next_circ_id,
@@ -1581,10 +1580,10 @@ choose_good_middle_server(uint8_t purpose,
/** Pick a good entry server for the circuit to be built according to
* <b>state</b>. Don't reuse a chosen exit (if any), don't use this
* router (if we're an OR), and respect firewall settings; if we're
* using entry_guards, return one.
* configured to use entry guards, return one.
*
* If <b>state</b> is NULL, we're choosing routers to serve as entry
* nodes, not for any particular circuit.
* If <b>state</b> is NULL, we're choosing a router to serve as an entry
* guard, not for any particular circuit.
*/
static routerinfo_t *
choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
@@ -1721,49 +1720,62 @@ onion_append_hop(crypt_path_t **head_ptr, extend_info_t *choice)
return 0;
}
/** Allocate a new extend_info object based on the various arguments. */
extend_info_t *
extend_info_alloc(const char *nickname, const char *digest,
crypto_pk_env_t *onion_key,
uint32_t addr, uint16_t port)
{
extend_info_t *info = tor_malloc_zero(sizeof(extend_info_t));
memcpy(info->identity_digest, digest, DIGEST_LEN);
if (nickname)
strlcpy(info->nickname, nickname, sizeof(info->nickname));
else {
/* make one up */
}
if (onion_key)
info->onion_key = crypto_pk_dup_key(onion_key);
info->addr = addr;
info->port = port;
return info;
}
/** Allocate and return a new extend_info_t that can be used to build a
* circuit to or through the router <b>r</b>. */
extend_info_t *
extend_info_from_router(routerinfo_t *r)
{
extend_info_t *info;
tor_assert(r);
info = tor_malloc_zero(sizeof(extend_info_t));
strlcpy(info->nickname, r->nickname, sizeof(info->nickname));
memcpy(info->identity_digest, r->cache_info.identity_digest, DIGEST_LEN);
info->onion_key = crypto_pk_dup_key(r->onion_pkey);
info->addr = r->addr;
info->port = r->or_port;
info->router_purpose = r->purpose;
return info;
return extend_info_alloc(r->nickname, r->cache_info.identity_digest,
r->onion_pkey, r->addr, r->or_port);
}
#if 0
/** What router purpose is <b>digest</b>?
* It's a general purpose router unless it's on our bridges list.
*/
static uint8_t
get_router_purpose_from_digest(char *digest)
{
(void)digest;
return ROUTER_PURPOSE_GENERAL; /* XXX020 */
if (digest_is_a_bridge(digest))
return ROUTER_PURPOSE_BRIDGE;
return ROUTER_PURPOSE_GENERAL;
}
#endif
#if 0
/** Allocate and return a new extend_info_t that can be used to build a
* circuit to or through the router <b>r</b>. */
extend_info_t *
extend_info_from_routerstatus(routerstatus_t *s)
{
extend_info_t *info;
tor_assert(s);
info = tor_malloc_zero(sizeof(extend_info_t));
strlcpy(info->nickname, s->nickname, sizeof(info->nickname));
memcpy(info->identity_digest, s->identity_digest, DIGEST_LEN);
info->onion_key = NULL; /* routerstatus doesn't know this */
info->addr = s->addr;
info->port = s->or_port;
info->router_purpose = get_router_purpose_from_digest(info->identity_digest);
return info;
/* routerstatus doesn't know onion_key; leave it NULL */
return extend_info_alloc(s->nickname, s->identity_digest,
NULL, s->addr, s->or_port);
// get_router_purpose_from_digest(s->identity_digest));
}
#endif
/** Release storage held by an extend_info_t struct. */
void
@@ -1834,7 +1846,9 @@ entry_guard_set_status(entry_guard_t *e, routerinfo_t *ri,
reason = "unlisted";
else if (!ri->is_running)
reason = "down";
else if (!ri->is_possible_guard &&
else if (options->UseBridges && ri->purpose != ROUTER_PURPOSE_BRIDGE)
reason = "not a bridge";
else if (!options->UseBridges && !ri->is_possible_guard &&
!router_nickname_is_in_list(ri, options->EntryNodes))
reason = "not recommended as a guard";
else if (router_nickname_is_in_list(ri, options->ExcludeNodes))
@@ -1909,6 +1923,10 @@ entry_is_live(entry_guard_t *e, int need_uptime, int need_capacity,
r = router_get_by_digest(e->identity);
if (!r)
return NULL;
if (get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_BRIDGE)
return NULL;
if (!get_options()->UseBridges && r->purpose != ROUTER_PURPOSE_GENERAL)
return NULL;
if (router_is_unreliable(r, need_uptime, need_capacity, 0))
return NULL;
if (firewall_is_fascist_or() &&
@@ -2337,18 +2355,33 @@ entry_guards_prepend_from_config(void)
entry_guards_changed();
}
/** Pick a live (up and listed) entry guard from entry_guards, and
* make sure not to pick this circuit's exit. */
static routerinfo_t *
/** Return 1 if we're fine adding arbitrary routers out of the
* directory to our entry guard list. Else return 0. */
static int
can_grow_entry_list(or_options_t *options)
{
if (options->StrictEntryNodes)
return 0;
if (options->UseBridges)
return 0;
return 1;
}
/** Pick a live (up and listed) entry guard from entry_guards. If
* <b>state</b> is non-NULL, this is for a specific circuit --
* make sure not to pick this circuit's exit or any node in the
* exit's family. If <b>state</b> is NULL, we're looking for a random
* guard (likely a bridge). */
routerinfo_t *
choose_random_entry(cpath_build_state_t *state)
{
or_options_t *options = get_options();
smartlist_t *live_entry_guards = smartlist_create();
smartlist_t *exit_family = smartlist_create();
routerinfo_t *chosen_exit = build_state_get_exit_router(state);
routerinfo_t *chosen_exit = state?build_state_get_exit_router(state) : NULL;
routerinfo_t *r = NULL;
int need_uptime = state->need_uptime;
int need_capacity = state->need_capacity;
int need_uptime = state ? state->need_uptime : 0;
int need_capacity = state ? state->need_capacity : 0;
if (chosen_exit) {
smartlist_add(exit_family, chosen_exit);
@@ -2361,7 +2394,7 @@ choose_random_entry(cpath_build_state_t *state)
if (should_add_entry_nodes)
entry_guards_prepend_from_config();
if (!options->StrictEntryNodes &&
if (can_grow_entry_list(options) &&
(! entry_guards ||
smartlist_len(entry_guards) < options->NumEntryGuards))
pick_entry_guards();
@@ -2383,7 +2416,7 @@ choose_random_entry(cpath_build_state_t *state)
* using him.
* (We might get 2 live-but-crummy entry guards, but so be it.) */
if (smartlist_len(live_entry_guards) < 2) {
if (!options->StrictEntryNodes) {
if (can_grow_entry_list(options)) {
/* still no? try adding a new entry then */
/* XXX if guard doesn't imply fast and stable, then we need
* to tell add_an_entry_guard below what we want, or it might
@@ -2403,7 +2436,7 @@ choose_random_entry(cpath_build_state_t *state)
need_capacity = 0;
goto retry;
}
/* live_entry_guards will be empty below. Oh well, we tried. */
/* live_entry_guards may be empty below. Oh well, we tried. */
}
r = smartlist_choose(live_entry_guards);
@@ -2641,6 +2674,18 @@ clear_bridge_list(void)
smartlist_clear(bridge_list);
}
/** Return 1 if <b>digest</b> is one of our known bridges. */
int
identity_digest_is_a_bridge(const char *digest)
{
SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge,
{
if (!memcmp(bridge->identity, digest, DIGEST_LEN))
return 1;
});
return 0;
}
/** Remember a new bridge at <b>addr</b>:<b>port</b>. If <b>digest</b>
* is set, it tells us the identity key too. */
void
@@ -2660,7 +2705,7 @@ bridge_add_from_config(uint32_t addr, uint16_t port, char *digest)
* descriptor, fetch a new copy of its descriptor -- either directly
* from the bridge or via a bridge authority. */
void
learn_bridge_descriptors(void)
fetch_bridge_descriptors(void)
{
char address_buf[INET_NTOA_BUF_LEN+1];
struct in_addr in;
@@ -2698,3 +2743,41 @@ learn_bridge_descriptors(void)
});
}
/** We just learned a descriptor for a bridge. See if that
* digest is in our entry guard list, and add it if not. */
void
learned_bridge_descriptor(routerinfo_t *ri)
{
tor_assert(ri);
tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE);
if (get_options()->UseBridges) {
ri->is_running = 1;
add_an_entry_guard(ri);
log_notice(LD_DIR, "new bridge descriptor '%s'", ri->nickname);
}
}
/** Return 1 if any of our entry guards have descriptors that
* are marked with purpose 'bridge'. Else return 0.
*
* We use this function to decide if we're ready to start building
* circuits through our bridges, or if we need to wait until the
* directory "server/authority" requests finish. */
int
any_bridge_descriptors_known(void)
{
return choose_random_entry(NULL)!=NULL ? 1 : 0;
#if 0
routerinfo_t *ri;
if (!entry_guards)
entry_guards = smartlist_create();
SMARTLIST_FOREACH(entry_guards, entry_guard_t *, e,
{
ri = router_get_by_digest(e->identity);
if (ri && ri->purpose == ROUTER_PURPOSE_BRIDGE)
return 1;
});
return 0;
#endif
}
+14 -10
View File
@@ -1034,18 +1034,22 @@ circuit_get_open_circ_or_launch(edge_connection_t *conn,
want_onehop, conn->chosen_exit_name);
if (want_onehop && conn->chosen_exit_name[0] == '$') {
/* We're asking for a one-hop circuit to a router that
* we don't have a routerinfo about. Hope we have a
* routerstatus or equivalent. */
routerstatus_t *s =
routerstatus_get_by_hexdigest(conn->chosen_exit_name+1);
if (s) {
extend_info = extend_info_from_routerstatus(s);
} else {
log_warn(LD_APP,
"Requested router '%s' is not known. Closing.",
conn->chosen_exit_name);
* we don't have a routerinfo about. Make up an extend_info. */
char digest[DIGEST_LEN];
char *hexdigest = conn->chosen_exit_name+1;
struct in_addr in;
if (strlen(hexdigest) < HEX_DIGEST_LEN ||
base16_decode(digest,DIGEST_LEN,hexdigest,HEX_DIGEST_LEN)<0) {
log_info(LD_DIR, "Broken exit digest on tunnel conn. Closing.");
return -1;
}
if (!tor_inet_aton(conn->socks_request->address, &in)) {
log_info(LD_DIR, "Broken address on tunnel conn. Closing.");
return -1;
}
extend_info = extend_info_alloc(conn->chosen_exit_name+1,
digest, NULL, ntohl(in.s_addr),
conn->socks_request->port);
} else {
/* We will need an onion key for the router, and we
* don't have one. Refuse or relax requirements. */
+4
View File
@@ -2914,6 +2914,10 @@ options_validate(or_options_t *old_options, or_options_t *options,
}
}
if (options->UseBridges && !options->Bridges)
REJECT("If you set UseBridges, you must specify at least one bridge.");
if (options->UseBridges && !options->TunnelDirConns)
REJECT("If you set UseBridges, you must set TunnelDirConns.");
if (options->Bridges) {
for (cl = options->Bridges; cl; cl = cl->next) {
if (parse_bridge_line(cl->value, 1)<0)
+25 -10
View File
@@ -230,7 +230,21 @@ directory_get_from_dirserver(uint8_t dir_purpose, const char *resource,
if (!options->FetchServerDescriptors && type != HIDSERV_AUTHORITY)
return;
if (directconn) {
if (directconn && options->UseBridges) {
/* want to pick a bridge for which we have a descriptor. */
routerinfo_t *ri = choose_random_entry(NULL);
if (ri) {
directory_initiate_command(ri->address, ri->addr,
ri->or_port, 0,
1, ri->cache_info.identity_digest,
dir_purpose,
ROUTER_PURPOSE_GENERAL,
0, resource, NULL, 0);
} else
log_notice(LD_DIR, "Ignoring directory request, since no bridge "
"nodes are available yet.");
return;
} else if (directconn) {
if (prefer_authority) {
/* only ask authdirservers, and don't ask myself */
rs = router_pick_trusteddirserver(type, 1, 1,
@@ -259,8 +273,7 @@ directory_get_from_dirserver(uint8_t dir_purpose, const char *resource,
directconn = 0; /* last resort: try routing it via Tor */
}
}
}
if (!directconn) {
} else { /* !directconn */
/* Never use fascistfirewall; we're going via Tor. */
if (dir_purpose == DIR_PURPOSE_FETCH_RENDDESC) {
/* only ask hidserv authorities, any of them will do */
@@ -463,7 +476,7 @@ directory_initiate_command(const char *address, uint32_t addr,
tor_assert(address);
tor_assert(addr);
tor_assert(dir_port);
tor_assert(or_port || dir_port);
tor_assert(digest);
log_debug(LD_DIR, "anonymized %d, want_to_tunnel %d.",
@@ -1221,7 +1234,7 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
smartlist_t *which = NULL;
int n_asked_for = 0;
log_info(LD_DIR,"Received %s (size %d) from server '%s:%d'",
was_ei ? "server info" : "extra server info",
was_ei ? "extra server info" : "server info",
(int)body_len, conn->_base.address, conn->_base.port);
if (was_ei)
note_request(was_compressed?"dl/extra.z":"dl/extra", orig_len);
@@ -1255,13 +1268,15 @@ connection_dir_client_reached_eof(dir_connection_t *conn)
tor_free(body); tor_free(headers); tor_free(reason);
return dir_okay ? 0 : -1;
}
/* Learn the routers, assuming we requested by fingerprint or "all".
* Right now, we only use "authority" to fetch ourself, so we don't want
* to risk replacing ourself with a router running at the addr:port we
* think we have.
/* Learn the routers, assuming we requested by fingerprint or "all"
* or "authority". (We use "authority" to fetch our own descriptor for
* testing, and to fetch bridge descriptors for bootstrapping.)
*/
/* XXX020 We now risk replacing ourself with a router running at
* the addr:port we think we have. Might want to check more carefully. */
if (which || (conn->requested_resource &&
!strcmpstart(conn->requested_resource, "all"))) {
(!strcmpstart(conn->requested_resource, "all") ||
!strcmpstart(conn->requested_resource, "authority")))) {
/* as we learn from them, we remove them from 'which' */
if (was_ei) {
router_load_extrainfo_from_string(body, NULL, SAVED_NOWHERE, which);
+3
View File
@@ -884,6 +884,8 @@ run_scheduled_events(time_t now)
if (time_to_reset_descriptor_failures < now) {
router_reset_descriptor_download_failures();
if (options->UseBridges)
fetch_bridge_descriptors(); /* XXX get this its own retry schedule -RD */
time_to_reset_descriptor_failures =
now + DESCRIPTOR_FAILURE_RESET_INTERVAL;
}
@@ -951,6 +953,7 @@ run_scheduled_events(time_t now)
* and the rend cache. */
rep_history_clean(now - options->RephistTrackTime);
rend_cache_clean();
/* XXX020 we only clean this stuff if DirPort is set?! -RD */
}
/* 2b. Once per minute, regenerate and upload the descriptor if the old
+13 -5
View File
@@ -1384,7 +1384,7 @@ typedef struct extend_info_t {
* display. */
char identity_digest[DIGEST_LEN]; /**< Hash of this router's identity key. */
uint16_t port; /**< OR port. */
uint8_t router_purpose; /**< General, controller, or bridge. */
// uint8_t router_purpose; /**< General, controller, or bridge. */
uint32_t addr; /**< IP address in host order. */
crypto_pk_env_t *onion_key; /**< Current onionskin key. */
} extend_info_t;
@@ -2200,8 +2200,11 @@ int circuit_all_predicted_ports_handled(time_t now, int *need_uptime,
int circuit_append_new_exit(origin_circuit_t *circ, extend_info_t *info);
int circuit_extend_to_new_exit(origin_circuit_t *circ, extend_info_t *info);
void onion_append_to_cpath(crypt_path_t **head_ptr, crypt_path_t *new_hop);
extend_info_t *extend_info_alloc(const char *nickname, const char *digest,
crypto_pk_env_t *onion_key,
uint32_t addr, uint16_t port);
extend_info_t *extend_info_from_router(routerinfo_t *r);
extend_info_t *extend_info_from_routerstatus(routerstatus_t *s);
//extend_info_t *extend_info_from_routerstatus(routerstatus_t *s);
extend_info_t *extend_info_dup(extend_info_t *info);
void extend_info_free(extend_info_t *info);
routerinfo_t *build_state_get_exit_router(cpath_build_state_t *state);
@@ -2211,15 +2214,19 @@ void entry_guards_compute_status(void);
int entry_guard_register_connect_status(const char *digest, int succeeded,
time_t now);
void entry_nodes_should_be_added(void);
void entry_guards_update_state(or_state_t *state);
routerinfo_t *choose_random_entry(cpath_build_state_t *state);
int entry_guards_parse_state(or_state_t *state, int set, char **msg);
void entry_guards_update_state(or_state_t *state);
int getinfo_helper_entry_guards(control_connection_t *conn,
const char *question, char **answer);
void entry_guards_free_all(void);
void clear_bridge_list(void);
int identity_digest_is_a_bridge(const char *digest);
void bridge_add_from_config(uint32_t addr, uint16_t port, char *digest);
void learn_bridge_descriptors(void);
void fetch_bridge_descriptors(void);
void learned_bridge_descriptor(routerinfo_t *ri);
int any_bridge_descriptors_known(void);
/********************************* circuitlist.c ***********************/
@@ -3315,7 +3322,8 @@ local_routerstatus_t *router_get_combined_status_by_digest(const char *digest);
local_routerstatus_t *router_get_combined_status_by_descriptor_digest(
const char *digest);
routerstatus_t *routerstatus_get_by_hexdigest(const char *hexdigest);
//routerstatus_t *routerstatus_get_by_hexdigest(const char *hexdigest);
int should_delay_dir_fetches(or_options_t *options);
void update_networkstatus_downloads(time_t now);
void update_router_descriptor_downloads(time_t now);
void update_extrainfo_downloads(time_t now);
+47 -6
View File
@@ -2340,7 +2340,7 @@ router_set_status(const char *digest, int up)
*
* This function should be called *after*
* routers_update_status_from_networkstatus; subsequently, you should call
* router_rebuild_store and control_event_descriptors_changed.
* router_rebuild_store and routerlist_descriptors_added.
*/
int
router_add_to_routerlist(routerinfo_t *router, const char **msg,
@@ -2387,7 +2387,8 @@ router_add_to_routerlist(routerinfo_t *router, const char **msg,
/* Only check the descriptor digest against the network statuses when
* we are receiving in response to a fetch. */
if (!signed_desc_digest_is_recognized(&router->cache_info)) {
if (!signed_desc_digest_is_recognized(&router->cache_info) &&
!identity_digest_is_a_bridge(router->cache_info.identity_digest)) {
/* We asked for it, so some networkstatus must have listed it when we
* did. Save it if we're a cache in case somebody else asks for it. */
log_info(LD_DIR,
@@ -2725,6 +2726,19 @@ routerlist_remove_old_routers(void)
digestmap_free(retain, NULL);
}
/** We just added a new descriptor that isn't of purpose
* ROUTER_PURPOSE_GENERAL. Take whatever extra steps we need. */
static void
routerlist_descriptors_added(smartlist_t *sl)
{
tor_assert(sl);
control_event_descriptors_changed(sl);
SMARTLIST_FOREACH(sl, routerinfo_t *, ri,
if (ri->purpose == ROUTER_PURPOSE_BRIDGE)
learned_bridge_descriptor(ri);
);
}
/**
* Code to parse a single router descriptor and insert it into the
* routerlist. Return -1 if the descriptor was ill-formed; 0 if the
@@ -2753,7 +2767,7 @@ router_load_single_router(const char *s, uint8_t purpose, const char **msg)
return -1;
}
ri->purpose = purpose;
if (purpose != ROUTER_PURPOSE_GENERAL)
if (ri->purpose != ROUTER_PURPOSE_GENERAL)
ri->cache_info.do_not_cache = 1;
if (router_is_me(ri)) {
log_warn(LD_DIR, "Router's identity key matches mine; dropping.");
@@ -2774,7 +2788,7 @@ router_load_single_router(const char *s, uint8_t purpose, const char **msg)
smartlist_free(lst);
return 0;
} else {
control_event_descriptors_changed(lst);
routerlist_descriptors_added(lst);
smartlist_free(lst);
log_debug(LD_DIR, "Added router to list");
return 1;
@@ -2837,7 +2851,7 @@ router_load_routers_from_string(const char *s, const char *eos,
});
if (smartlist_len(changed))
control_event_descriptors_changed(changed);
routerlist_descriptors_added(changed);
routerlist_assert_ok(routerlist);
router_rebuild_store(0, 0);
@@ -3272,6 +3286,7 @@ router_get_combined_status_by_nickname(const char *nickname,
return best;
}
#if 0
/** Find a routerstatus_t that corresponds to <b>hexdigest</b>, if
* any. Prefer ones that belong to authorities. */
routerstatus_t *
@@ -3290,6 +3305,7 @@ routerstatus_get_by_hexdigest(const char *hexdigest)
return &(rs->status);
return NULL;
}
#endif
/** Return true iff any networkstatus includes a descriptor whose digest
* is that of <b>desc</b>. */
@@ -3503,11 +3519,26 @@ update_networkstatus_client_downloads(time_t now)
smartlist_free(missing);
}
/** Return 1 if there's a reason we shouldn't try any directory
* fetches yet (e.g. we demand bridges and none are yet known).
* Else return 0. */
int
should_delay_dir_fetches(or_options_t *options)
{
if (options->UseBridges && !any_bridge_descriptors_known()) {
log_notice(LD_DIR, "delaying dir fetches");
return 1;
}
return 0;
}
/** Launch requests for networkstatus documents as appropriate. */
void
update_networkstatus_downloads(time_t now)
{
or_options_t *options = get_options();
if (should_delay_dir_fetches(options))
return;
if (options->DirPort)
update_networkstatus_cache_downloads(now);
else
@@ -4729,6 +4760,8 @@ void
update_router_descriptor_downloads(time_t now)
{
or_options_t *options = get_options();
if (should_delay_dir_fetches(options))
return;
if (options->DirPort) {
update_router_descriptor_cache_downloads(now);
} else {
@@ -4753,7 +4786,7 @@ should_download_extrainfo(signed_descriptor_t *sd,
!digestmap_get(pending, d));
}
/** Laucnch extrainfo downloads as needed. */
/** Launch extrainfo downloads as needed. */
void
update_extrainfo_downloads(time_t now)
{
@@ -4764,6 +4797,8 @@ update_extrainfo_downloads(time_t now)
int i;
if (! options->DownloadExtraInfo)
return;
if (should_delay_dir_fetches(options))
return;
pending = digestmap_new();
list_pending_descriptor_downloads(pending, 1);
@@ -4853,6 +4888,12 @@ update_router_have_minimum_dir_info(void)
routerlist_remove_old_routers();
networkstatus_list_clean(now);
if (should_delay_dir_fetches(get_options())) {
log_notice(LD_DIR, "no bridge descs known yet");
res = 0;
goto done;
}
n_authorities = get_n_v2_authorities();
n_ns = smartlist_len(networkstatus_list);
if (n_ns<=n_authorities/2) {