/* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. * Copyright (c) 2007-2016, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** * \file bridges.c * \brief Code to manage bridges and bridge selection. * * Bridges are fixed entry nodes, used for censorship circumvention. **/ #include "or.h" #include "bridges.h" #include "circuitbuild.h" #include "config.h" #include "connection.h" #include "directory.h" #include "entrynodes.h" #include "nodelist.h" #include "policies.h" #include "router.h" #include "routerlist.h" #include "routerset.h" #include "transports.h" /** Information about a configured bridge. Currently this just matches the * ones in the torrc file, but one day we may be able to learn about new * bridges on our own, and remember them in the state file. */ typedef struct { /** Address of the bridge. */ tor_addr_t addr; /** TLS port for the bridge. */ uint16_t port; /** Boolean: We are re-parsing our bridge list, and we are going to remove * this one if we don't find it in the list of configured bridges. */ unsigned marked_for_removal : 1; /** Expected identity digest, or all zero bytes if we don't know what the * digest should be. */ char identity[DIGEST_LEN]; /** Name of pluggable transport protocol taken from its config line. */ char *transport_name; /** When should we next try to fetch a descriptor for this bridge? */ download_status_t fetch_status; /** A smartlist of k=v values to be passed to the SOCKS proxy, if transports are used for this bridge. */ smartlist_t *socks_args; } bridge_info_t; static void bridge_free(bridge_info_t *bridge); static int num_bridges_usable(void); /** A list of configured bridges. Whenever we actually get a descriptor * for one, we add it as an entry guard. Note that the order of bridges * in this list does not necessarily correspond to the order of bridges * in the torrc. */ static smartlist_t *bridge_list = NULL; /** Mark every entry of the bridge list to be removed on our next call to * sweep_bridge_list unless it has first been un-marked. */ void mark_bridge_list(void) { if (!bridge_list) bridge_list = smartlist_new(); SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, b->marked_for_removal = 1); } /** Remove every entry of the bridge list that was marked with * mark_bridge_list if it has not subsequently been un-marked. */ void sweep_bridge_list(void) { if (!bridge_list) bridge_list = smartlist_new(); SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { if (b->marked_for_removal) { SMARTLIST_DEL_CURRENT(bridge_list, b); bridge_free(b); } } SMARTLIST_FOREACH_END(b); } /** Initialize the bridge list to empty, creating it if needed. */ static void clear_bridge_list(void) { if (!bridge_list) bridge_list = smartlist_new(); SMARTLIST_FOREACH(bridge_list, bridge_info_t *, b, bridge_free(b)); smartlist_clear(bridge_list); } /** Free the bridge bridge. */ static void bridge_free(bridge_info_t *bridge) { if (!bridge) return; tor_free(bridge->transport_name); if (bridge->socks_args) { SMARTLIST_FOREACH(bridge->socks_args, char*, s, tor_free(s)); smartlist_free(bridge->socks_args); } tor_free(bridge); } /** If we have a bridge configured whose digest matches digest, or a * bridge with no known digest whose address matches any of the * tor_addr_port_t's in orports, return that bridge. Else return * NULL. */ static bridge_info_t * get_configured_bridge_by_orports_digest(const char *digest, const smartlist_t *orports) { if (!bridge_list) return NULL; SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { if (tor_digest_is_zero(bridge->identity)) { SMARTLIST_FOREACH_BEGIN(orports, tor_addr_port_t *, ap) { if (tor_addr_compare(&bridge->addr, &ap->addr, CMP_EXACT) == 0 && bridge->port == ap->port) return bridge; } SMARTLIST_FOREACH_END(ap); } if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) return bridge; } SMARTLIST_FOREACH_END(bridge); return NULL; } /** If we have a bridge configured whose digest matches digest, or a * bridge with no known digest whose address matches addr:port, * return that bridge. Else return NULL. If digest is NULL, check for * address/port matches only. */ static bridge_info_t * get_configured_bridge_by_addr_port_digest(const tor_addr_t *addr, uint16_t port, const char *digest) { if (!bridge_list) return NULL; SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { if ((tor_digest_is_zero(bridge->identity) || digest == NULL) && !tor_addr_compare(&bridge->addr, addr, CMP_EXACT) && bridge->port == port) return bridge; if (digest && tor_memeq(bridge->identity, digest, DIGEST_LEN)) return bridge; } SMARTLIST_FOREACH_END(bridge); return NULL; } /** If we have a bridge configured whose digest matches digest, or a * bridge with no known digest whose address matches addr:port, * return 1. Else return 0. If digest is NULL, check for * address/port matches only. */ int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, const char *digest) { tor_assert(addr); return get_configured_bridge_by_addr_port_digest(addr, port, digest) ? 1 : 0; } /** If we have a bridge configured whose digest matches * ei->identity_digest, or a bridge with no known digest whose address * matches ei->addr:ei->port, return 1. Else return 0. * If ei->onion_key is NULL, check for address/port matches only. */ int extend_info_is_a_configured_bridge(const extend_info_t *ei) { const char *digest = ei->onion_key ? ei->identity_digest : NULL; return addr_is_a_configured_bridge(&ei->addr, ei->port, digest); } /** Wrapper around get_configured_bridge_by_addr_port_digest() to look * it up via router descriptor ri. */ static bridge_info_t * get_configured_bridge_by_routerinfo(const routerinfo_t *ri) { bridge_info_t *bi = NULL; smartlist_t *orports = router_get_all_orports(ri); bi = get_configured_bridge_by_orports_digest(ri->cache_info.identity_digest, orports); SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); smartlist_free(orports); return bi; } /** Return 1 if ri is one of our known bridges, else 0. */ int routerinfo_is_a_configured_bridge(const routerinfo_t *ri) { return get_configured_bridge_by_routerinfo(ri) ? 1 : 0; } /** Return 1 if node is one of our configured bridges, else 0. */ int node_is_a_configured_bridge(const node_t *node) { int retval = 0; smartlist_t *orports = node_get_all_orports(node); retval = get_configured_bridge_by_orports_digest(node->identity, orports) != NULL; SMARTLIST_FOREACH(orports, tor_addr_port_t *, p, tor_free(p)); smartlist_free(orports); return retval; } /** We made a connection to a router at addr:port * without knowing its digest. Its digest turned out to be digest. * If it was a bridge, and we still don't know its digest, record it. */ void learned_router_identity(const tor_addr_t *addr, uint16_t port, const char *digest) { bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, port, digest); if (bridge && tor_digest_is_zero(bridge->identity)) { char *transport_info = NULL; const char *transport_name = find_transport_name_by_bridge_addrport(addr, port); if (transport_name) tor_asprintf(&transport_info, " (with transport '%s')", transport_name); memcpy(bridge->identity, digest, DIGEST_LEN); log_notice(LD_DIR, "Learned fingerprint %s for bridge %s%s.", hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), transport_info ? transport_info : ""); tor_free(transport_info); } } /** Return true if bridge has the same identity digest as * digest. If digest is NULL, it matches * bridges with unspecified identity digests. */ static int bridge_has_digest(const bridge_info_t *bridge, const char *digest) { if (digest) return tor_memeq(digest, bridge->identity, DIGEST_LEN); else return tor_digest_is_zero(bridge->identity); } /** We are about to add a new bridge at addr:port, with optional * digest and transport_name. Mark for removal any previously * existing bridge with the same address and port, and warn the user as * appropriate. */ static void bridge_resolve_conflicts(const tor_addr_t *addr, uint16_t port, const char *digest, const char *transport_name) { /* Iterate the already-registered bridge list: If you find a bridge with the same adress and port, mark it for removal. It doesn't make sense to have two active bridges with the same IP:PORT. If the bridge in question has a different digest or transport than digest/transport_name, it's probably a misconfiguration and we should warn the user. */ SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { if (bridge->marked_for_removal) continue; if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { bridge->marked_for_removal = 1; if (!bridge_has_digest(bridge, digest) || strcmp_opt(bridge->transport_name, transport_name)) { /* warn the user */ char *bridge_description_new, *bridge_description_old; tor_asprintf(&bridge_description_new, "%s:%s:%s", fmt_addrport(addr, port), digest ? hex_str(digest, DIGEST_LEN) : "", transport_name ? transport_name : ""); tor_asprintf(&bridge_description_old, "%s:%s:%s", fmt_addrport(&bridge->addr, bridge->port), tor_digest_is_zero(bridge->identity) ? "" : hex_str(bridge->identity,DIGEST_LEN), bridge->transport_name ? bridge->transport_name : ""); log_warn(LD_GENERAL,"Tried to add bridge '%s', but we found a conflict" " with the already registered bridge '%s'. We will discard" " the old bridge and keep '%s'. If this is not what you" " wanted, please change your configuration file accordingly.", bridge_description_new, bridge_description_old, bridge_description_new); tor_free(bridge_description_new); tor_free(bridge_description_old); } } } SMARTLIST_FOREACH_END(bridge); } /** Return True if we have a bridge that uses a transport with name * transport_name. */ MOCK_IMPL(int, transport_is_needed, (const char *transport_name)) { if (!bridge_list) return 0; SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { if (bridge->transport_name && !strcmp(bridge->transport_name, transport_name)) return 1; } SMARTLIST_FOREACH_END(bridge); return 0; } /** Register the bridge information in bridge_line to the * bridge subsystem. Steals reference of bridge_line. */ void bridge_add_from_config(bridge_line_t *bridge_line) { bridge_info_t *b; { /* Log the bridge we are about to register: */ log_debug(LD_GENERAL, "Registering bridge at %s (transport: %s) (%s)", fmt_addrport(&bridge_line->addr, bridge_line->port), bridge_line->transport_name ? bridge_line->transport_name : "no transport", tor_digest_is_zero(bridge_line->digest) ? "no key listed" : hex_str(bridge_line->digest, DIGEST_LEN)); if (bridge_line->socks_args) { /* print socks arguments */ int i = 0; tor_assert(smartlist_len(bridge_line->socks_args) > 0); log_debug(LD_GENERAL, "Bridge uses %d SOCKS arguments:", smartlist_len(bridge_line->socks_args)); SMARTLIST_FOREACH(bridge_line->socks_args, const char *, arg, log_debug(LD_CONFIG, "%d: %s", ++i, arg)); } } bridge_resolve_conflicts(&bridge_line->addr, bridge_line->port, bridge_line->digest, bridge_line->transport_name); b = tor_malloc_zero(sizeof(bridge_info_t)); tor_addr_copy(&b->addr, &bridge_line->addr); b->port = bridge_line->port; memcpy(b->identity, bridge_line->digest, DIGEST_LEN); if (bridge_line->transport_name) b->transport_name = bridge_line->transport_name; b->fetch_status.schedule = DL_SCHED_BRIDGE; b->fetch_status.backoff = DL_SCHED_RANDOM_EXPONENTIAL; b->socks_args = bridge_line->socks_args; if (!bridge_list) bridge_list = smartlist_new(); tor_free(bridge_line); /* Deallocate bridge_line now. */ smartlist_add(bridge_list, b); } /** Return true iff routerset contains the bridge bridge. */ static int routerset_contains_bridge(const routerset_t *routerset, const bridge_info_t *bridge) { int result; extend_info_t *extinfo; tor_assert(bridge); if (!routerset) return 0; extinfo = extend_info_new( NULL, bridge->identity, NULL, NULL, &bridge->addr, bridge->port); result = routerset_contains_extendinfo(routerset, extinfo); extend_info_free(extinfo); return result; } /** If digest is one of our known bridges, return it. */ static bridge_info_t * find_bridge_by_digest(const char *digest) { SMARTLIST_FOREACH(bridge_list, bridge_info_t *, bridge, { if (tor_memeq(bridge->identity, digest, DIGEST_LEN)) return bridge; }); return NULL; } /** Given the addr and port of a bridge, if that bridge * supports a pluggable transport, return its name. Otherwise, return * NULL. */ const char * find_transport_name_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) { if (!bridge_list) return NULL; SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) return bridge->transport_name; } SMARTLIST_FOREACH_END(bridge); return NULL; } /** If addr and port match the address and port of a * bridge of ours that uses pluggable transports, place its transport * in transport. * * Return 0 on success (found a transport, or found a bridge with no * transport, or found no bridge); return -1 if we should be using a * transport, but the transport could not be found. */ int get_transport_by_bridge_addrport(const tor_addr_t *addr, uint16_t port, const transport_t **transport) { *transport = NULL; if (!bridge_list) return 0; SMARTLIST_FOREACH_BEGIN(bridge_list, const bridge_info_t *, bridge) { if (tor_addr_eq(&bridge->addr, addr) && (bridge->port == port)) { /* bridge matched */ if (bridge->transport_name) { /* it also uses pluggable transports */ *transport = transport_get_by_name(bridge->transport_name); if (*transport == NULL) { /* it uses pluggable transports, but the transport could not be found! */ return -1; } return 0; } else { /* bridge matched, but it doesn't use transports. */ break; } } } SMARTLIST_FOREACH_END(bridge); *transport = NULL; return 0; } /** Return a smartlist containing all the SOCKS arguments that we * should pass to the SOCKS proxy. */ const smartlist_t * get_socks_args_by_bridge_addrport(const tor_addr_t *addr, uint16_t port) { bridge_info_t *bridge = get_configured_bridge_by_addr_port_digest(addr, port, NULL); return bridge ? bridge->socks_args : NULL; } /** We need to ask bridge for its server descriptor. */ static void launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) { const or_options_t *options = get_options(); if (connection_get_by_type_addr_port_purpose( CONN_TYPE_DIR, &bridge->addr, bridge->port, DIR_PURPOSE_FETCH_SERVERDESC)) return; /* it's already on the way */ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { download_status_mark_impossible(&bridge->fetch_status); log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", safe_str_client(fmt_and_decorate_addr(&bridge->addr))); return; } /* Until we get a descriptor for the bridge, we only know one address for * it. */ if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, FIREWALL_OR_CONNECTION, 0, 0)) { log_notice(LD_CONFIG, "Tried to fetch a descriptor directly from a " "bridge, but that bridge is not reachable through our " "firewall."); return; } directory_initiate_command(&bridge->addr, bridge->port, NULL, 0, /*no dirport*/ bridge->identity, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, DIRIND_ONEHOP, "authority.z", NULL, 0, 0); } /** Fetching the bridge descriptor from the bridge authority returned a * "not found". Fall back to trying a direct fetch. */ void retry_bridge_descriptor_fetch_directly(const char *digest) { bridge_info_t *bridge = find_bridge_by_digest(digest); if (!bridge) return; /* not found? oh well. */ launch_direct_bridge_descriptor_fetch(bridge); } /** For each bridge in our list for which we don't currently have a * descriptor, fetch a new copy of its descriptor -- either directly * from the bridge or via a bridge authority. */ void fetch_bridge_descriptors(const or_options_t *options, time_t now) { int num_bridge_auths = get_n_authorities(BRIDGE_DIRINFO); int ask_bridge_directly; int can_use_bridge_authority; if (!bridge_list) return; /* If we still have unconfigured managed proxies, don't go and connect to a bridge. */ if (pt_proxies_configuration_pending()) return; SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, bridge) { if (!download_status_is_ready(&bridge->fetch_status, now, IMPOSSIBLE_TO_DOWNLOAD)) continue; /* don't bother, no need to retry yet */ if (routerset_contains_bridge(options->ExcludeNodes, bridge)) { download_status_mark_impossible(&bridge->fetch_status); log_warn(LD_APP, "Not using bridge at %s: it is in ExcludeNodes.", safe_str_client(fmt_and_decorate_addr(&bridge->addr))); continue; } /* schedule another fetch as if this one will fail, in case it does */ download_status_failed(&bridge->fetch_status, 0); can_use_bridge_authority = !tor_digest_is_zero(bridge->identity) && num_bridge_auths; ask_bridge_directly = !can_use_bridge_authority || !options->UpdateBridgesFromAuthority; log_debug(LD_DIR, "ask_bridge_directly=%d (%d, %d, %d)", ask_bridge_directly, tor_digest_is_zero(bridge->identity), !options->UpdateBridgesFromAuthority, !num_bridge_auths); if (ask_bridge_directly && !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, FIREWALL_OR_CONNECTION, 0, 0)) { log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our " "firewall policy. %s.", fmt_addrport(&bridge->addr, bridge->port), can_use_bridge_authority ? "Asking bridge authority instead" : "Skipping"); if (can_use_bridge_authority) ask_bridge_directly = 0; else continue; } if (ask_bridge_directly) { /* we need to ask the bridge itself for its descriptor. */ launch_direct_bridge_descriptor_fetch(bridge); } else { /* We have a digest and we want to ask an authority. We could * combine all the requests into one, but that may give more * hints to the bridge authority than we want to give. */ char resource[10 + HEX_DIGEST_LEN]; memcpy(resource, "fp/", 3); base16_encode(resource+3, HEX_DIGEST_LEN+1, bridge->identity, DIGEST_LEN); memcpy(resource+3+HEX_DIGEST_LEN, ".z", 3); log_info(LD_DIR, "Fetching bridge info '%s' from bridge authority.", resource); directory_get_from_dirserver(DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_BRIDGE, resource, 0, DL_WANT_AUTHORITY); } } SMARTLIST_FOREACH_END(bridge); } /** If our bridge is configured to be a different address than * the bridge gives in node, rewrite the routerinfo * we received to use the address we meant to use. Now we handle * multihomed bridges better. */ static void rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) { /* XXXX move this function. */ /* XXXX overridden addresses should really live in the node_t, so that the * routerinfo_t and the microdesc_t can be immutable. But we can only * do that safely if we know that no function that connects to an OR * does so through an address from any source other than node_get_addr(). */ tor_addr_t addr; const or_options_t *options = get_options(); if (node->ri) { routerinfo_t *ri = node->ri; tor_addr_from_ipv4h(&addr, ri->addr); if ((!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && bridge->port == ri->or_port) || (!tor_addr_compare(&bridge->addr, &ri->ipv6_addr, CMP_EXACT) && bridge->port == ri->ipv6_orport)) { /* they match, so no need to do anything */ } else { if (tor_addr_family(&bridge->addr) == AF_INET) { ri->addr = tor_addr_to_ipv4h(&bridge->addr); ri->or_port = bridge->port; log_info(LD_DIR, "Adjusted bridge routerinfo for '%s' to match configured " "address %s:%d.", ri->nickname, fmt_addr32(ri->addr), ri->or_port); } else if (tor_addr_family(&bridge->addr) == AF_INET6) { tor_addr_copy(&ri->ipv6_addr, &bridge->addr); ri->ipv6_orport = bridge->port; log_info(LD_DIR, "Adjusted bridge routerinfo for '%s' to match configured " "address %s.", ri->nickname, fmt_addrport(&ri->ipv6_addr, ri->ipv6_orport)); } else { log_err(LD_BUG, "Address family not supported: %d.", tor_addr_family(&bridge->addr)); return; } } if (options->ClientPreferIPv6ORPort == -1) { /* Mark which address to use based on which bridge_t we got. */ node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && !tor_addr_is_null(&node->ri->ipv6_addr)); } else { /* Mark which address to use based on user preference */ node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) && !tor_addr_is_null(&node->ri->ipv6_addr)); } /* XXXipv6 we lack support for falling back to another address for the same relay, warn the user */ if (!tor_addr_is_null(&ri->ipv6_addr)) { tor_addr_port_t ap; node_get_pref_orport(node, &ap); log_notice(LD_CONFIG, "Bridge '%s' has both an IPv4 and an IPv6 address. " "Will prefer using its %s address (%s) based on %s.", ri->nickname, node->ipv6_preferred ? "IPv6" : "IPv4", fmt_addrport(&ap.addr, ap.port), options->ClientPreferIPv6ORPort == -1 ? "the configured Bridge address" : "ClientPreferIPv6ORPort"); } } if (node->rs) { routerstatus_t *rs = node->rs; tor_addr_from_ipv4h(&addr, rs->addr); if (!tor_addr_compare(&bridge->addr, &addr, CMP_EXACT) && bridge->port == rs->or_port) { /* they match, so no need to do anything */ } else { rs->addr = tor_addr_to_ipv4h(&bridge->addr); rs->or_port = bridge->port; log_info(LD_DIR, "Adjusted bridge routerstatus for '%s' to match " "configured address %s.", rs->nickname, fmt_addrport(&bridge->addr, rs->or_port)); } } } /** 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, int from_cache) { tor_assert(ri); tor_assert(ri->purpose == ROUTER_PURPOSE_BRIDGE); if (get_options()->UseBridges) { int first = num_bridges_usable() <= 1; bridge_info_t *bridge = get_configured_bridge_by_routerinfo(ri); time_t now = time(NULL); router_set_status(ri->cache_info.identity_digest, 1); if (bridge) { /* if we actually want to use this one */ node_t *node; /* it's here; schedule its re-fetch for a long time from now. */ if (!from_cache) download_status_reset(&bridge->fetch_status); node = node_get_mutable_by_id(ri->cache_info.identity_digest); tor_assert(node); rewrite_node_address_for_bridge(bridge, node); if (tor_digest_is_zero(bridge->identity)) { memcpy(bridge->identity,ri->cache_info.identity_digest, DIGEST_LEN); log_notice(LD_DIR, "Learned identity %s for bridge at %s:%d", hex_str(bridge->identity, DIGEST_LEN), fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } add_an_entry_guard(get_guard_selection_info(), node, 1, 1, 0, 0); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, from_cache ? "cached" : "fresh", router_describe(ri)); /* set entry->made_contact so if it goes down we don't drop it from * our entry node list */ entry_guard_register_connect_status(ri->cache_info.identity_digest, 1, 0, now); if (first) { routerlist_retry_directory_downloads(now); } } } } /** Return the number of bridges that have descriptors that * are marked with purpose 'bridge' and are running. * * 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) { tor_assert(get_options()->UseBridges); return choose_random_entry(NULL) != NULL; } /** Return the number of bridges that have descriptors that are marked with * purpose 'bridge' and are running. */ static int num_bridges_usable(void) { int n_options = 0; tor_assert(get_options()->UseBridges); (void) choose_random_entry_impl(get_guard_selection_info(), NULL, 0, 0, &n_options); return n_options; } /** Return a smartlist containing all bridge identity digests */ MOCK_IMPL(smartlist_t *, list_bridge_identities, (void)) { smartlist_t *result = NULL; char *digest_tmp; if (get_options()->UseBridges && bridge_list) { result = smartlist_new(); SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { digest_tmp = tor_malloc(DIGEST_LEN); memcpy(digest_tmp, b->identity, DIGEST_LEN); smartlist_add(result, digest_tmp); } SMARTLIST_FOREACH_END(b); } return result; } /** Get the download status for a bridge descriptor given its identity */ MOCK_IMPL(download_status_t *, get_bridge_dl_status_by_id, (const char *digest)) { download_status_t *dl = NULL; if (digest && get_options()->UseBridges && bridge_list) { SMARTLIST_FOREACH_BEGIN(bridge_list, bridge_info_t *, b) { if (tor_memeq(digest, b->identity, DIGEST_LEN)) { dl = &(b->fetch_status); break; } } SMARTLIST_FOREACH_END(b); } return dl; } /** Release all storage held in bridges.c */ void bridges_free_all(void) { clear_bridge_list(); smartlist_free(bridge_list); bridge_list = NULL; }