diff --git a/src/common/address.c b/src/common/address.c index 773e688554..1bb0c077d1 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -2121,3 +2121,11 @@ tor_addr_port_new(const tor_addr_t *addr, uint16_t port) return ap; } +/** Return true iff a and b are the same address and port */ +int +tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b) +{ + return tor_addr_eq(&a->addr, &b->addr) && a->port == b->port; +} + diff --git a/src/common/address.h b/src/common/address.h index 51db42c315..41daf012e6 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -342,6 +342,8 @@ get_interface_address_list(int severity, int include_internal) } tor_addr_port_t *tor_addr_port_new(const tor_addr_t *addr, uint16_t port); +int tor_addr_port_eq(const tor_addr_port_t *a, + const tor_addr_port_t *b); #ifdef ADDRESS_PRIVATE MOCK_DECL(smartlist_t *,get_interface_addresses_raw,(int severity, diff --git a/src/or/bridges.c b/src/or/bridges.c index 2170cc668a..f16acfa28a 100644 --- a/src/or/bridges.c +++ b/src/or/bridges.c @@ -28,7 +28,9 @@ /** 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 { +struct bridge_info_t { + /** Address and port of the bridge, as configured by the user.*/ + tor_addr_port_t addrport_configured; /** Address of the bridge. */ tor_addr_t addr; /** TLS port for the bridge. */ @@ -49,7 +51,7 @@ typedef struct { /** 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); @@ -111,6 +113,40 @@ bridge_free(bridge_info_t *bridge) tor_free(bridge); } +/** Return a list of all the configured bridges, as bridge_info_t pointers. */ +const smartlist_t * +bridge_list_get(void) +{ + if (!bridge_list) + bridge_list = smartlist_new(); + return bridge_list; +} + +/** + * Given a bridge, return a pointer to its RSA identity digest, or + * NULL if we don't know one for it. + */ +const uint8_t * +bridge_get_rsa_id_digest(const bridge_info_t *bridge) +{ + tor_assert(bridge); + if (tor_digest_is_zero(bridge->identity)) + return NULL; + else + return (const uint8_t *) bridge->identity; +} + +/** + * Given a bridge, return a pointer to its configured addr:port + * combination. + */ +const tor_addr_port_t * +bridge_get_addr_port(const bridge_info_t *bridge) +{ + tor_assert(bridge); + return &bridge->addrport_configured; +} + /** 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 @@ -243,6 +279,7 @@ learned_router_identity(const tor_addr_t *addr, uint16_t port, hex_str(digest, DIGEST_LEN), fmt_addrport(addr, port), transport_info ? transport_info : ""); tor_free(transport_info); + // XXXX prop271 here. we will need to update the guard info too. } } @@ -361,6 +398,8 @@ bridge_add_from_config(bridge_line_t *bridge_line) bridge_line->transport_name); b = tor_malloc_zero(sizeof(bridge_info_t)); + tor_addr_copy(&b->addrport_configured.addr, &bridge_line->addr); + b->addrport_configured.port = bridge_line->port; tor_addr_copy(&b->addr, &bridge_line->addr); b->port = bridge_line->port; memcpy(b->identity, bridge_line->digest, DIGEST_LEN); @@ -718,6 +757,7 @@ learned_bridge_descriptor(routerinfo_t *ri, int from_cache) fmt_and_decorate_addr(&bridge->addr), (int) bridge->port); } + // XXXX prop271 here we will need to update the guard info too. add_bridge_as_entry_guard(get_guard_selection_info(), node); log_notice(LD_DIR, "new bridge descriptor '%s' (%s): %s", ri->nickname, diff --git a/src/or/bridges.h b/src/or/bridges.h index 738b1a68ee..d01794f573 100644 --- a/src/or/bridges.h +++ b/src/or/bridges.h @@ -14,8 +14,14 @@ struct bridge_line_t; +/* Opaque handle to a configured bridge */ +typedef struct bridge_info_t bridge_info_t; + void mark_bridge_list(void); void sweep_bridge_list(void); +const smartlist_t *bridge_list_get(void); +const uint8_t *bridge_get_rsa_id_digest(const bridge_info_t *bridge); +const tor_addr_port_t * bridge_get_addr_port(const bridge_info_t *bridge); int addr_is_a_configured_bridge(const tor_addr_t *addr, uint16_t port, const char *digest); diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 0795d19026..e725d4e5ae 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -163,6 +163,10 @@ static int node_is_possible_guard(guard_selection_t *gs, const node_t *node); static int node_passes_guard_filter(const or_options_t *options, guard_selection_t *gs, const node_t *node); +static entry_guard_t *entry_guard_add_to_sample_impl(guard_selection_t *gs, + const uint8_t *rsa_id_digest, + const char *nickname, + const tor_addr_port_t *bridge_addrport); /** Return 0 if we should apply guardfraction information found in the * consensus. A specific consensus can be specified with the @@ -693,25 +697,47 @@ STATIC entry_guard_t * entry_guard_add_to_sample(guard_selection_t *gs, const node_t *node) { - const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; - tor_assert(gs); - tor_assert(node); - log_info(LD_GUARD, "Adding %s as to the entry guard sample set.", node_describe(node)); + return entry_guard_add_to_sample_impl(gs, + (const uint8_t*)node->identity, + node_get_nickname(node), + NULL); +} + +/** + * Backend: adds a new sampled guard to gs, with given identity, + * nickname, and ORPort. rsa_id_digest and bridge_addrport are + * optional, but we need one of them. nickname is optional. + */ +static entry_guard_t * +entry_guard_add_to_sample_impl(guard_selection_t *gs, + const uint8_t *rsa_id_digest, + const char *nickname, + const tor_addr_port_t *bridge_addrport) +{ + const int GUARD_LIFETIME = get_guard_lifetime_days() * 86400; + tor_assert(gs); + // XXXX prop271 take ed25519 identity here too. /* make sure that the guard is not already sampled. */ - if (BUG(have_sampled_guard_with_id(gs, (uint8_t*)node->identity))) + if (rsa_id_digest && BUG(have_sampled_guard_with_id(gs, rsa_id_digest))) + return NULL; // LCOV_EXCL_LINE + /* Make sure we can actually identify the guard. */ + if (BUG(!rsa_id_digest && !bridge_addrport)) return NULL; // LCOV_EXCL_LINE entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); /* persistent fields */ + guard->is_persistent = (rsa_id_digest != NULL); guard->selection_name = tor_strdup(gs->name); - memcpy(guard->identity, node->identity, DIGEST_LEN); - strlcpy(guard->nickname, node_get_nickname(node), sizeof(guard->nickname)); + if (rsa_id_digest) + memcpy(guard->identity, rsa_id_digest, DIGEST_LEN); + if (nickname) + strlcpy(guard->nickname, nickname, sizeof(guard->nickname)); guard->sampled_on_date = randomize_time(approx_time(), GUARD_LIFETIME/10); tor_free(guard->sampled_by_version); guard->sampled_by_version = tor_strdup(VERSION); @@ -720,6 +746,8 @@ entry_guard_add_to_sample(guard_selection_t *gs, /* non-persistent fields */ guard->is_reachable = GUARD_REACHABLE_MAYBE; + if (bridge_addrport) + guard->bridge_addr = tor_memdup(bridge_addrport, sizeof(*bridge_addrport)); smartlist_add(gs->sampled_entry_guards, guard); guard->in_selection = gs; @@ -728,6 +756,87 @@ entry_guard_add_to_sample(guard_selection_t *gs, return guard; } +/** + * Add an entry guard to the "bridges" guard selection sample, with + * information taken from bridge. Return that entry guard. + */ +entry_guard_t * +entry_guard_add_bridge_to_sample(const bridge_info_t *bridge) +{ + guard_selection_t *gs = get_guard_selection_by_name("bridges", + GS_TYPE_BRIDGE, + 1); + const uint8_t *id_digest = bridge_get_rsa_id_digest(bridge); + const tor_addr_port_t *addrport = bridge_get_addr_port(bridge); + + tor_assert(addrport); + + return entry_guard_add_to_sample_impl(gs, id_digest, NULL, addrport); +} + +/** + * Return the entry_guard_t in gs whose address is addrport, + * or NULL if none exists. +*/ +static entry_guard_t * +entry_guard_get_by_bridge_addr(guard_selection_t *gs, + const tor_addr_port_t *addrport) +{ + if (! gs) + return NULL; + if (BUG(!addrport)) + return NULL; + SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, g) { + if (g->bridge_addr && tor_addr_port_eq(addrport, g->bridge_addr)) + return g; + } SMARTLIST_FOREACH_END(g); + return NULL; +} + +/** Update the guard subsystem's knowledge of the identity of the bridge + * at addrport. Idempotent. + */ +void +entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, + const uint8_t *rsa_id_digest) +{ + guard_selection_t *gs = get_guard_selection_by_name("bridges", + GS_TYPE_BRIDGE, + 0); + if (!gs) + return; + + entry_guard_t *g = entry_guard_get_by_bridge_addr(gs, addrport); + if (!g) + return; + + int make_persistent = 0; + + if (tor_digest_is_zero(g->identity)) { + memcpy(g->identity, rsa_id_digest, DIGEST_LEN); + make_persistent = 1; + } else if (tor_memeq(g->identity, rsa_id_digest, DIGEST_LEN)) { + /* Nothing to see here; we learned something we already knew. */ + if (BUG(! g->is_persistent)) + make_persistent = 1; + } else { + char old_id[HEX_DIGEST_LEN+1]; + base16_encode(old_id, sizeof(old_id), g->identity, sizeof(g->identity)); + log_warn(LD_BUG, "We 'learned' an identity %s for a bridge at %s:%d, but " + "we already knew a different one (%s). Ignoring the new info as " + "possibly bogus.", + hex_str((const char *)rsa_id_digest, DIGEST_LEN), + fmt_and_decorate_addr(&addrport->addr), addrport->port, + old_id); + return; // redundant, but let's be clear: we're not making this persistent. + } + + if (make_persistent) { + g->is_persistent = 1; + entry_guards_changed_for_guard_selection(gs); + } +} + /** * Return the number of sampled guards in gs that are "filtered" * (that is, we're willing to connect to them) and that are "usable" @@ -892,14 +1001,16 @@ sampled_guards_update_from_consensus(guard_selection_t *gs) // It's important to use only a live consensus here; we don't want to // make changes based on anything expired or old. - networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); + if (gs->type != GS_TYPE_BRIDGE) { + networkstatus_t *ns = networkstatus_get_live_consensus(approx_time()); - log_info(LD_GUARD, "Updating sampled guard status based on received " - "consensus."); + log_info(LD_GUARD, "Updating sampled guard status based on received " + "consensus."); - if (! ns || ns->valid_until < approx_time()) { - log_info(LD_GUARD, "Hey, that consensus isn't still valid. Ignoring."); - return; + if (! ns || ns->valid_until < approx_time()) { + log_info(LD_GUARD, "Hey, there wasn't a valid consensus. Ignoring"); + return; + } } int n_changes = 0; @@ -2070,6 +2181,11 @@ entry_guard_encode_for_state(entry_guard_t *guard) smartlist_add_asprintf(result, "in=%s", guard->selection_name); smartlist_add_asprintf(result, "rsa_id=%s", hex_str(guard->identity, DIGEST_LEN)); + if (guard->bridge_addr) { + smartlist_add_asprintf(result, "bridge_addr=%s:%d", + fmt_and_decorate_addr(&guard->bridge_addr->addr), + guard->bridge_addr->port); + } if (strlen(guard->nickname)) { smartlist_add_asprintf(result, "nickname=%s", guard->nickname); } @@ -2152,6 +2268,7 @@ entry_guard_parse_from_state(const char *s) char *listed = NULL; char *confirmed_on = NULL; char *confirmed_idx = NULL; + char *bridge_addr = NULL; // pathbias char *pb_use_attempts = NULL; @@ -2180,6 +2297,7 @@ entry_guard_parse_from_state(const char *s) FIELD(listed); FIELD(confirmed_on); FIELD(confirmed_idx); + FIELD(bridge_addr); FIELD(pb_use_attempts); FIELD(pb_use_successes); FIELD(pb_circ_attempts); @@ -2218,6 +2336,7 @@ entry_guard_parse_from_state(const char *s) } entry_guard_t *guard = tor_malloc_zero(sizeof(entry_guard_t)); + guard->is_persistent = 1; if (in == NULL) { log_warn(LD_CIRC, "Guard missing 'in' field"); @@ -2247,6 +2366,16 @@ entry_guard_parse_from_state(const char *s) guard->identity, DIGEST_LEN); } + if (bridge_addr) { + tor_addr_port_t res; + memset(&res, 0, sizeof(res)); + int r = tor_addr_port_parse(LOG_WARN, bridge_addr, + &res.addr, &res.port, -1); + if (r == 0) + guard->bridge_addr = tor_memdup(&res, sizeof(res)); + /* On error, we already warned. */ + } + /* Process the various time fields. */ #define HANDLE_TIME(field) do { \ @@ -2357,6 +2486,7 @@ entry_guard_parse_from_state(const char *s) tor_free(listed); tor_free(confirmed_on); tor_free(confirmed_idx); + tor_free(bridge_addr); tor_free(pb_use_attempts); tor_free(pb_use_successes); tor_free(pb_circ_attempts); @@ -2388,6 +2518,8 @@ entry_guards_update_guards_in_state(or_state_t *state) if (!strcmp(gs->name, "legacy")) continue; /* This is encoded differently. */ SMARTLIST_FOREACH_BEGIN(gs->sampled_entry_guards, entry_guard_t *, guard) { + if (guard->is_persistent == 0) + continue; *nextline = tor_malloc_zero(sizeof(config_line_t)); (*nextline)->key = tor_strdup("Guard"); (*nextline)->value = entry_guard_encode_for_state(guard); @@ -2908,6 +3040,7 @@ add_an_entry_guard(guard_selection_t *gs, return NULL; } entry = tor_malloc_zero(sizeof(entry_guard_t)); + entry->is_persistent = 1; log_info(LD_CIRC, "Chose %s as new entry guard.", node_describe(node)); strlcpy(entry->nickname, node_get_nickname(node), sizeof(entry->nickname)); @@ -3020,6 +3153,7 @@ entry_guard_free(entry_guard_t *e) tor_free(e->sampled_by_version); tor_free(e->extra_state_fields); tor_free(e->selection_name); + tor_free(e->bridge_addr); tor_free(e); } @@ -3840,6 +3974,7 @@ entry_guards_parse_state_for_guard_selection( node = tor_malloc_zero(sizeof(entry_guard_t)); /* all entry guards on disk have been contacted */ node->made_contact = 1; + node->is_persistent = 1; smartlist_add(new_entry_guards, node); smartlist_split_string(args, line->value, " ", SPLIT_SKIP_SPACE|SPLIT_IGNORE_BLANK, 0); diff --git a/src/or/entrynodes.h b/src/or/entrynodes.h index 0abbea8cb6..cbc3f89cb4 100644 --- a/src/or/entrynodes.h +++ b/src/or/entrynodes.h @@ -125,7 +125,9 @@ struct entry_guard_t { * Which selection does this guard belong to? */ char *selection_name; - guard_selection_t *in_selection; + + /** Bridges only: address of the bridge. */ + tor_addr_port_t *bridge_addr; /* ==== Non-persistent fields. */ /* == These are used by sampled guards */ @@ -140,6 +142,9 @@ struct entry_guard_t { * to try again until it either succeeds or fails. Primary guards can * never be pending. */ unsigned is_pending : 1; + /** If true, don't write this guard to disk. (Used for bridges with unknown + * identities) */ + unsigned is_persistent : 1; /** When did we get the earliest connection failure for this guard? * We clear this field on a successful connect. We do _not_ clear it * when we mark the guard as "MAYBE" reachable. @@ -160,6 +165,9 @@ struct entry_guard_t { /** This string holds any fields that we are maintaining because * we saw them in the state, even if we don't understand them. */ char *extra_state_fields; + + /** Backpointer to the guard selection that this guard belongs to. */ + guard_selection_t *in_selection; /**@}*/ /** @@ -554,6 +562,14 @@ STATIC int entry_is_time_to_retry(const entry_guard_t *e, time_t now); void remove_all_entry_guards_for_guard_selection(guard_selection_t *gs); void remove_all_entry_guards(void); +struct bridge_info_t; +// XXXX prop271 should this be a public API? +entry_guard_t *entry_guard_add_bridge_to_sample( + const struct bridge_info_t *bridge); + +void entry_guard_learned_bridge_identity(const tor_addr_port_t *addrport, + const uint8_t *rsa_id_digest); + void entry_guards_compute_status_for_guard_selection( guard_selection_t *gs, const or_options_t *options, time_t now); void entry_guards_compute_status(const or_options_t *options, time_t now); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index bc0862a93e..32af7ffd5d 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -1055,6 +1055,9 @@ test_entry_guard_encode_for_state_maximal(void *arg) strlcpy(eg->nickname, "Fred", sizeof(eg->nickname)); eg->selection_name = tor_strdup("default"); memcpy(eg->identity, "plurpyflurpyslurpydo", DIGEST_LEN); + eg->bridge_addr = tor_malloc_zero(sizeof(tor_addr_port_t)); + tor_addr_from_ipv4h(&eg->bridge_addr->addr, 0x08080404); + eg->bridge_addr->port = 9999; eg->sampled_on_date = 1479081600; eg->sampled_by_version = tor_strdup("1.2.3"); eg->unlisted_since_date = 1479081645; @@ -1069,6 +1072,7 @@ test_entry_guard_encode_for_state_maximal(void *arg) tt_str_op(s, OP_EQ, "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=8.8.4.4:9999 " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " "sampled_by=1.2.3 " @@ -1100,6 +1104,7 @@ test_entry_guard_parse_from_state_minimal(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "596f75206d6179206e656564206120686f626279"); tt_str_op(eg->nickname, OP_EQ, "$596F75206D6179206E656564206120686F626279"); + tt_ptr_op(eg->bridge_addr, OP_EQ, NULL); tt_i64_op(eg->sampled_on_date, OP_GE, t); tt_i64_op(eg->sampled_on_date, OP_LE, t+86400); tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); @@ -1126,6 +1131,7 @@ test_entry_guard_parse_from_state_maximal(void *arg) eg = entry_guard_parse_from_state( "in=fred " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=[1::3]:9999 " "nickname=Fred " "sampled_on=2016-11-14T00:00:00 " "sampled_by=1.2.3 " @@ -1139,6 +1145,8 @@ test_entry_guard_parse_from_state_maximal(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "706C75727079666C75727079736C75727079646F"); + tt_str_op(fmt_addr(&eg->bridge_addr->addr), OP_EQ, "1::3"); + tt_int_op(eg->bridge_addr->port, OP_EQ, 9999); tt_str_op(eg->nickname, OP_EQ, "Fred"); tt_i64_op(eg->sampled_on_date, OP_EQ, 1479081600); tt_i64_op(eg->unlisted_since_date, OP_EQ, 1479081645); @@ -1205,6 +1213,7 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) eg = entry_guard_parse_from_state( "in=default " "rsa_id=706C75727079666C75727079736C75727079646F " + "bridge_addr=1.2.3.3.4:5 " "nickname=FredIsANodeWithAStrangeNicknameThatIsTooLong " "sampled_on=2016-11-14T00:00:99 " "sampled_by=1.2.3 stuff in the middle " @@ -1219,6 +1228,7 @@ test_entry_guard_parse_from_state_partial_failure(void *arg) test_mem_op_hex(eg->identity, OP_EQ, "706C75727079666C75727079736C75727079646F"); tt_str_op(eg->nickname, OP_EQ, "FredIsANodeWithAStrangeNicknameThatIsTooL"); + tt_ptr_op(eg->bridge_addr, OP_EQ, NULL); tt_i64_op(eg->sampled_on_date, OP_EQ, t); tt_i64_op(eg->unlisted_since_date, OP_EQ, 0); tt_str_op(eg->sampled_by_version, OP_EQ, "1.2.3");