Loading changes/bug24767 0 → 100644 +5 −0 Original line number Diff line number Diff line o Major bugfixes (relay, connection): - Refuse to connect again to a relay from which we failed previously with a connection refused, timeout or error (at the TCP level). The relay won't be retried for 60 seconds after the failure occured. Fixes bug 24767; bugfix on 0.0.6. src/or/connection_or.c +225 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ * part of a subclass (channel_tls_t). */ #define TOR_CHANNEL_INTERNAL_ #define CONNECTION_OR_PRIVATE #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" Loading Loading @@ -1122,6 +1123,216 @@ connection_or_group_set_badness_(smartlist_t *group, int force) } SMARTLIST_FOREACH_END(or_conn); } /* Lifetime of a connection failure. After that, we'll retry. This is in * seconds. */ #define OR_CONNECT_FAILURE_LIFETIME 60 /* The interval to use with when to clean up the failure cache. */ #define OR_CONNECT_FAILURE_CLEANUP_INTERVAL 60 /* When is the next time we have to cleanup the failure map. We keep this * because we clean it opportunistically. */ static time_t or_connect_failure_map_next_cleanup_ts = 0; /* OR connection failure entry data structure. It is kept in the connection * failure map defined below and indexed by OR identity digest, address and * port. * * We need to identify a connection failure with these three values because we * want to avoid to wrongfully blacklist a relay if someone is trying to * extend to a known identity digest but with the wrong IP/port. For instance, * it can happen if a relay changed its port but the client still has an old * descriptor with the old port. We want to stop connecting to that * IP/port/identity all together, not only the relay identity. */ typedef struct or_connect_failure_entry_t { HT_ENTRY(or_connect_failure_entry_t) node; /* Identity digest of the connection where it is connecting to. */ uint8_t identity_digest[DIGEST_LEN]; /* This is the connection address from the base connection_t. After the * connection is checked for canonicity, the base address should represent * what we know instead of where we are connecting to. This is what we need * so we can correlate known relays within the consensus. */ tor_addr_t addr; uint16_t port; /* Last time we were unable to connect. */ time_t last_failed_connect_ts; } or_connect_failure_entry_t; /* Map where we keep connection failure entries. They are indexed by addr, * port and identity digest. */ static HT_HEAD(or_connect_failure_ht, or_connect_failure_entry_t) or_connect_failures_map = HT_INITIALIZER(); /* Helper: Hashtable equal function. Return 1 if equal else 0. */ static int or_connect_failure_ht_eq(const or_connect_failure_entry_t *a, const or_connect_failure_entry_t *b) { return fast_memeq(a->identity_digest, b->identity_digest, DIGEST_LEN) && tor_addr_eq(&a->addr, &b->addr) && a->port == b->port; } /* Helper: Return the hash for the hashtable of the given entry. For this * table, it is a combination of address, port and identity digest. */ static unsigned int or_connect_failure_ht_hash(const or_connect_failure_entry_t *entry) { size_t offset = 0, addr_size; const void *addr_ptr; /* Largest size is IPv6 and IPv4 is smaller so it is fine. */ uint8_t data[16 + sizeof(uint16_t) + DIGEST_LEN]; /* Get the right address bytes depending on the family. */ switch (tor_addr_family(&entry->addr)) { case AF_INET: addr_size = 4; addr_ptr = &entry->addr.addr.in_addr.s_addr; break; case AF_INET6: addr_size = 16; addr_ptr = &entry->addr.addr.in6_addr.s6_addr; break; default: tor_assert_nonfatal_unreached(); return 0; } memcpy(data, addr_ptr, addr_size); offset += addr_size; memcpy(data + offset, entry->identity_digest, DIGEST_LEN); offset += DIGEST_LEN; set_uint16(data + offset, entry->port); offset += sizeof(uint16_t); return (unsigned int) siphash24g(data, offset); } HT_PROTOTYPE(or_connect_failure_ht, or_connect_failure_entry_t, node, or_connect_failure_ht_hash, or_connect_failure_ht_eq) HT_GENERATE2(or_connect_failure_ht, or_connect_failure_entry_t, node, or_connect_failure_ht_hash, or_connect_failure_ht_eq, 0.6, tor_reallocarray_, tor_free_) /* Initialize a given connect failure entry with the given identity_digest, * addr and port. All field are optional except ocf. */ static void or_connect_failure_init(const char *identity_digest, const tor_addr_t *addr, uint16_t port, or_connect_failure_entry_t *ocf) { tor_assert(ocf); if (identity_digest) { memcpy(ocf->identity_digest, identity_digest, sizeof(ocf->identity_digest)); } if (addr) { tor_addr_copy(&ocf->addr, addr); } ocf->port = port; } /* Return a newly allocated connection failure entry. It is initialized with * the given or_conn data. This can't fail. */ static or_connect_failure_entry_t * or_connect_failure_new(const or_connection_t *or_conn) { or_connect_failure_entry_t *ocf = tor_malloc_zero(sizeof(*ocf)); or_connect_failure_init(or_conn->identity_digest, &or_conn->real_addr, TO_CONN(or_conn)->port, ocf); return ocf; } /* Return a connection failure entry matching the given or_conn. NULL is * returned if not found. */ static or_connect_failure_entry_t * or_connect_failure_find(const or_connection_t *or_conn) { or_connect_failure_entry_t lookup; tor_assert(or_conn); or_connect_failure_init(or_conn->identity_digest, &TO_CONN(or_conn)->addr, TO_CONN(or_conn)->port, &lookup); return HT_FIND(or_connect_failure_ht, &or_connect_failures_map, &lookup); } /* Note down in the connection failure cache that a failure occurred on the * given or_conn. */ STATIC void note_or_connect_failed(const or_connection_t *or_conn) { or_connect_failure_entry_t *ocf = NULL; tor_assert(or_conn); ocf = or_connect_failure_find(or_conn); if (ocf == NULL) { ocf = or_connect_failure_new(or_conn); HT_INSERT(or_connect_failure_ht, &or_connect_failures_map, ocf); } ocf->last_failed_connect_ts = approx_time(); } /* Cleanup the connection failure cache and remove all entries below the * given cutoff. */ static void or_connect_failure_map_cleanup(time_t cutoff) { or_connect_failure_entry_t **ptr, **next, *entry; for (ptr = HT_START(or_connect_failure_ht, &or_connect_failures_map); ptr != NULL; ptr = next) { entry = *ptr; if (entry->last_failed_connect_ts <= cutoff) { next = HT_NEXT_RMV(or_connect_failure_ht, &or_connect_failures_map, ptr); tor_free(entry); } else { next = HT_NEXT(or_connect_failure_ht, &or_connect_failures_map, ptr); } } } /* Return true iff the given OR connection can connect to its destination that * is the triplet identity_digest, address and port. * * The or_conn MUST have gone through connection_or_check_canonicity() so the * base address is properly set to what we know or doesn't know. */ STATIC int should_connect_to_relay(const or_connection_t *or_conn) { time_t now, cutoff; time_t connect_failed_since_ts = 0; or_connect_failure_entry_t *ocf; tor_assert(or_conn); now = approx_time(); cutoff = now - OR_CONNECT_FAILURE_LIFETIME; /* Opportunistically try to cleanup the failure cache. We do that at regular * interval so it doesn't grow too big. */ if (or_connect_failure_map_next_cleanup_ts <= now) { or_connect_failure_map_cleanup(cutoff); or_connect_failure_map_next_cleanup_ts = now + OR_CONNECT_FAILURE_CLEANUP_INTERVAL; } /* Look if we have failed previously to the same destination as this * OR connection. */ ocf = or_connect_failure_find(or_conn); if (ocf) { connect_failed_since_ts = ocf->last_failed_connect_ts; } /* If we do have an unable to connect timestamp and it is below cutoff, we * can connect. Or we have never failed before so let it connect. */ if (connect_failed_since_ts > cutoff) { goto no_connect; } /* Ok we can connect! */ return 1; no_connect: return 0; } /** <b>conn</b> is in the 'connecting' state, and it failed to complete * a TCP connection. Send notifications appropriately. * Loading @@ -1135,6 +1346,7 @@ connection_or_connect_failed(or_connection_t *conn, control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(get_options())) control_event_bootstrap_prob_or(msg, reason, conn); note_or_connect_failed(conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or Loading Loading @@ -1225,6 +1437,19 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, conn->chan = chan; chan->conn = conn; connection_or_init_conn_from_address(conn, &addr, port, id_digest, ed_id, 1); /* We have a proper OR connection setup, now check if we can connect to it * that is we haven't had a failure earlier. This is to avoid to try to * constantly connect to relays that we think are not reachable. */ if (!should_connect_to_relay(conn)) { log_info(LD_GENERAL, "Can't connect to identity %s at %s:%u because we " "failed earlier. Refusing.", hex_str(id_digest, DIGEST_LEN), fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); connection_free_(TO_CONN(conn)); return NULL; } connection_or_change_state(conn, OR_CONN_STATE_CONNECTING); control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0); Loading src/or/connection_or.h +5 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,11 @@ int connection_or_single_set_badness_(time_t now, int force); void connection_or_group_set_badness_(smartlist_t *group, int force); #ifdef CONNECTION_OR_PRIVATE STATIC int should_connect_to_relay(const or_connection_t *or_conn); STATIC void note_or_connect_failed(const or_connection_t *or_conn); #endif #ifdef TOR_UNIT_TESTS extern int certs_cell_ed25519_disabled_for_testing; #endif Loading src/or/nodelist.c +2 −2 Original line number Diff line number Diff line Loading @@ -161,8 +161,8 @@ init_nodelist(void) } /** As node_get_by_id, but returns a non-const pointer */ node_t * node_get_mutable_by_id(const char *identity_digest) MOCK_IMPL(node_t *, node_get_mutable_by_id,(const char *identity_digest)) { node_t search, *node; if (PREDICT_UNLIKELY(the_nodelist == NULL)) Loading src/or/nodelist.h +1 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ tor_assert((n)->ri || (n)->rs); \ } STMT_END node_t *node_get_mutable_by_id(const char *identity_digest); MOCK_DECL(node_t *, node_get_mutable_by_id,(const char *identity_digest)); MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); node_t *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); MOCK_DECL(const node_t *, node_get_by_ed25519_id, Loading Loading
changes/bug24767 0 → 100644 +5 −0 Original line number Diff line number Diff line o Major bugfixes (relay, connection): - Refuse to connect again to a relay from which we failed previously with a connection refused, timeout or error (at the TCP level). The relay won't be retried for 60 seconds after the failure occured. Fixes bug 24767; bugfix on 0.0.6.
src/or/connection_or.c +225 −0 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ * part of a subclass (channel_tls_t). */ #define TOR_CHANNEL_INTERNAL_ #define CONNECTION_OR_PRIVATE #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" Loading Loading @@ -1122,6 +1123,216 @@ connection_or_group_set_badness_(smartlist_t *group, int force) } SMARTLIST_FOREACH_END(or_conn); } /* Lifetime of a connection failure. After that, we'll retry. This is in * seconds. */ #define OR_CONNECT_FAILURE_LIFETIME 60 /* The interval to use with when to clean up the failure cache. */ #define OR_CONNECT_FAILURE_CLEANUP_INTERVAL 60 /* When is the next time we have to cleanup the failure map. We keep this * because we clean it opportunistically. */ static time_t or_connect_failure_map_next_cleanup_ts = 0; /* OR connection failure entry data structure. It is kept in the connection * failure map defined below and indexed by OR identity digest, address and * port. * * We need to identify a connection failure with these three values because we * want to avoid to wrongfully blacklist a relay if someone is trying to * extend to a known identity digest but with the wrong IP/port. For instance, * it can happen if a relay changed its port but the client still has an old * descriptor with the old port. We want to stop connecting to that * IP/port/identity all together, not only the relay identity. */ typedef struct or_connect_failure_entry_t { HT_ENTRY(or_connect_failure_entry_t) node; /* Identity digest of the connection where it is connecting to. */ uint8_t identity_digest[DIGEST_LEN]; /* This is the connection address from the base connection_t. After the * connection is checked for canonicity, the base address should represent * what we know instead of where we are connecting to. This is what we need * so we can correlate known relays within the consensus. */ tor_addr_t addr; uint16_t port; /* Last time we were unable to connect. */ time_t last_failed_connect_ts; } or_connect_failure_entry_t; /* Map where we keep connection failure entries. They are indexed by addr, * port and identity digest. */ static HT_HEAD(or_connect_failure_ht, or_connect_failure_entry_t) or_connect_failures_map = HT_INITIALIZER(); /* Helper: Hashtable equal function. Return 1 if equal else 0. */ static int or_connect_failure_ht_eq(const or_connect_failure_entry_t *a, const or_connect_failure_entry_t *b) { return fast_memeq(a->identity_digest, b->identity_digest, DIGEST_LEN) && tor_addr_eq(&a->addr, &b->addr) && a->port == b->port; } /* Helper: Return the hash for the hashtable of the given entry. For this * table, it is a combination of address, port and identity digest. */ static unsigned int or_connect_failure_ht_hash(const or_connect_failure_entry_t *entry) { size_t offset = 0, addr_size; const void *addr_ptr; /* Largest size is IPv6 and IPv4 is smaller so it is fine. */ uint8_t data[16 + sizeof(uint16_t) + DIGEST_LEN]; /* Get the right address bytes depending on the family. */ switch (tor_addr_family(&entry->addr)) { case AF_INET: addr_size = 4; addr_ptr = &entry->addr.addr.in_addr.s_addr; break; case AF_INET6: addr_size = 16; addr_ptr = &entry->addr.addr.in6_addr.s6_addr; break; default: tor_assert_nonfatal_unreached(); return 0; } memcpy(data, addr_ptr, addr_size); offset += addr_size; memcpy(data + offset, entry->identity_digest, DIGEST_LEN); offset += DIGEST_LEN; set_uint16(data + offset, entry->port); offset += sizeof(uint16_t); return (unsigned int) siphash24g(data, offset); } HT_PROTOTYPE(or_connect_failure_ht, or_connect_failure_entry_t, node, or_connect_failure_ht_hash, or_connect_failure_ht_eq) HT_GENERATE2(or_connect_failure_ht, or_connect_failure_entry_t, node, or_connect_failure_ht_hash, or_connect_failure_ht_eq, 0.6, tor_reallocarray_, tor_free_) /* Initialize a given connect failure entry with the given identity_digest, * addr and port. All field are optional except ocf. */ static void or_connect_failure_init(const char *identity_digest, const tor_addr_t *addr, uint16_t port, or_connect_failure_entry_t *ocf) { tor_assert(ocf); if (identity_digest) { memcpy(ocf->identity_digest, identity_digest, sizeof(ocf->identity_digest)); } if (addr) { tor_addr_copy(&ocf->addr, addr); } ocf->port = port; } /* Return a newly allocated connection failure entry. It is initialized with * the given or_conn data. This can't fail. */ static or_connect_failure_entry_t * or_connect_failure_new(const or_connection_t *or_conn) { or_connect_failure_entry_t *ocf = tor_malloc_zero(sizeof(*ocf)); or_connect_failure_init(or_conn->identity_digest, &or_conn->real_addr, TO_CONN(or_conn)->port, ocf); return ocf; } /* Return a connection failure entry matching the given or_conn. NULL is * returned if not found. */ static or_connect_failure_entry_t * or_connect_failure_find(const or_connection_t *or_conn) { or_connect_failure_entry_t lookup; tor_assert(or_conn); or_connect_failure_init(or_conn->identity_digest, &TO_CONN(or_conn)->addr, TO_CONN(or_conn)->port, &lookup); return HT_FIND(or_connect_failure_ht, &or_connect_failures_map, &lookup); } /* Note down in the connection failure cache that a failure occurred on the * given or_conn. */ STATIC void note_or_connect_failed(const or_connection_t *or_conn) { or_connect_failure_entry_t *ocf = NULL; tor_assert(or_conn); ocf = or_connect_failure_find(or_conn); if (ocf == NULL) { ocf = or_connect_failure_new(or_conn); HT_INSERT(or_connect_failure_ht, &or_connect_failures_map, ocf); } ocf->last_failed_connect_ts = approx_time(); } /* Cleanup the connection failure cache and remove all entries below the * given cutoff. */ static void or_connect_failure_map_cleanup(time_t cutoff) { or_connect_failure_entry_t **ptr, **next, *entry; for (ptr = HT_START(or_connect_failure_ht, &or_connect_failures_map); ptr != NULL; ptr = next) { entry = *ptr; if (entry->last_failed_connect_ts <= cutoff) { next = HT_NEXT_RMV(or_connect_failure_ht, &or_connect_failures_map, ptr); tor_free(entry); } else { next = HT_NEXT(or_connect_failure_ht, &or_connect_failures_map, ptr); } } } /* Return true iff the given OR connection can connect to its destination that * is the triplet identity_digest, address and port. * * The or_conn MUST have gone through connection_or_check_canonicity() so the * base address is properly set to what we know or doesn't know. */ STATIC int should_connect_to_relay(const or_connection_t *or_conn) { time_t now, cutoff; time_t connect_failed_since_ts = 0; or_connect_failure_entry_t *ocf; tor_assert(or_conn); now = approx_time(); cutoff = now - OR_CONNECT_FAILURE_LIFETIME; /* Opportunistically try to cleanup the failure cache. We do that at regular * interval so it doesn't grow too big. */ if (or_connect_failure_map_next_cleanup_ts <= now) { or_connect_failure_map_cleanup(cutoff); or_connect_failure_map_next_cleanup_ts = now + OR_CONNECT_FAILURE_CLEANUP_INTERVAL; } /* Look if we have failed previously to the same destination as this * OR connection. */ ocf = or_connect_failure_find(or_conn); if (ocf) { connect_failed_since_ts = ocf->last_failed_connect_ts; } /* If we do have an unable to connect timestamp and it is below cutoff, we * can connect. Or we have never failed before so let it connect. */ if (connect_failed_since_ts > cutoff) { goto no_connect; } /* Ok we can connect! */ return 1; no_connect: return 0; } /** <b>conn</b> is in the 'connecting' state, and it failed to complete * a TCP connection. Send notifications appropriately. * Loading @@ -1135,6 +1346,7 @@ connection_or_connect_failed(or_connection_t *conn, control_event_or_conn_status(conn, OR_CONN_EVENT_FAILED, reason); if (!authdir_mode_tests_reachability(get_options())) control_event_bootstrap_prob_or(msg, reason, conn); note_or_connect_failed(conn); } /** <b>conn</b> got an error in connection_handle_read_impl() or Loading Loading @@ -1225,6 +1437,19 @@ connection_or_connect, (const tor_addr_t *_addr, uint16_t port, conn->chan = chan; chan->conn = conn; connection_or_init_conn_from_address(conn, &addr, port, id_digest, ed_id, 1); /* We have a proper OR connection setup, now check if we can connect to it * that is we haven't had a failure earlier. This is to avoid to try to * constantly connect to relays that we think are not reachable. */ if (!should_connect_to_relay(conn)) { log_info(LD_GENERAL, "Can't connect to identity %s at %s:%u because we " "failed earlier. Refusing.", hex_str(id_digest, DIGEST_LEN), fmt_addr(&TO_CONN(conn)->addr), TO_CONN(conn)->port); connection_free_(TO_CONN(conn)); return NULL; } connection_or_change_state(conn, OR_CONN_STATE_CONNECTING); control_event_or_conn_status(conn, OR_CONN_EVENT_LAUNCHED, 0); Loading
src/or/connection_or.h +5 −0 Original line number Diff line number Diff line Loading @@ -120,6 +120,11 @@ int connection_or_single_set_badness_(time_t now, int force); void connection_or_group_set_badness_(smartlist_t *group, int force); #ifdef CONNECTION_OR_PRIVATE STATIC int should_connect_to_relay(const or_connection_t *or_conn); STATIC void note_or_connect_failed(const or_connection_t *or_conn); #endif #ifdef TOR_UNIT_TESTS extern int certs_cell_ed25519_disabled_for_testing; #endif Loading
src/or/nodelist.c +2 −2 Original line number Diff line number Diff line Loading @@ -161,8 +161,8 @@ init_nodelist(void) } /** As node_get_by_id, but returns a non-const pointer */ node_t * node_get_mutable_by_id(const char *identity_digest) MOCK_IMPL(node_t *, node_get_mutable_by_id,(const char *identity_digest)) { node_t search, *node; if (PREDICT_UNLIKELY(the_nodelist == NULL)) Loading
src/or/nodelist.h +1 −1 Original line number Diff line number Diff line Loading @@ -16,7 +16,7 @@ tor_assert((n)->ri || (n)->rs); \ } STMT_END node_t *node_get_mutable_by_id(const char *identity_digest); MOCK_DECL(node_t *, node_get_mutable_by_id,(const char *identity_digest)); MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); node_t *node_get_mutable_by_ed25519_id(const ed25519_public_key_t *ed_id); MOCK_DECL(const node_t *, node_get_by_ed25519_id, Loading