From e04e1a2e7d67861881c9e237dcea344dda39e858 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Mon, 27 Aug 2012 15:03:34 +0200 Subject: [PATCH 1/6] Clients connect to public relays over IPv6. Add ClientUseIPv6 and ClientPreferIPv6ORPort configuration options. Use "preferred OR port" for all entry nodes, not only for bridges. Mark bridges with "prefer IPv6 OR port" if an IPv6 address is configured in Bridge line and ClientPreferIPv6ORPort is set. Mark relays with "prefer IPv6 OR port" if an IPv6 address is found in descriptor and ClientPreferIPv6ORPort is set. Filter "preferred OR port" through the ClientUseIPv6 config option. We might want to move this test to where actual connection is being set up once we have a fall back mechanism in place. Have only non-servers pick an IPv6 address for the first hop: We don't want relays to connect over IPv6 yet. (IPv6 has never been used for second or third hops.) Implements ticket 5535. --- changes/bug5535 | 5 +++++ doc/tor.1.txt | 12 ++++++++++++ src/or/circuitbuild.c | 32 ++++++++++++++++++-------------- src/or/config.c | 2 ++ src/or/nodelist.c | 10 +++++++++- src/or/nodelist.h | 8 ++++---- src/or/or.h | 7 +++++++ 7 files changed, 57 insertions(+), 19 deletions(-) create mode 100644 changes/bug5535 diff --git a/changes/bug5535 b/changes/bug5535 new file mode 100644 index 0000000000..b5fa0d7ab7 --- /dev/null +++ b/changes/bug5535 @@ -0,0 +1,5 @@ + o Major features: + - If configured with ClientUseIPv6, clients may connect to entry + nodes over IPv6. Another new config option, ClientPreferIPv6ORPort, + can be set to make this even more likely to happen. + Implements ticket 5535. diff --git a/doc/tor.1.txt b/doc/tor.1.txt index aa9303c2fb..68898eb88f 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1171,6 +1171,18 @@ The following options are useful only for clients (that is, if If no defaults are available there, these options default to 20, .70, 0.0, 200, and 4 respectively. +**ClientUseIPv6** **0**|**1**:: + If this option is set to 1, Tor might connect to entry nodes over + IPv6. Note that clients configured with an IPv6 address in a + **Bridge** option will try connecting over IPv6 if even if + **ClientUseIPv6** is set to 0. (Default: 0) + +**ClientPreferIPv6ORPort** **0**|**1**:: + If this option is set to 1, Tor prefers an OR port with an IPv6 + address over one with IPv4 if a given entry node has both. Other + things may influence the choice. This option breaks a tie to the + favor of IPv6. (Default: 0) + SERVER OPTIONS -------------- diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index ba617ffa45..34197680b1 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -3792,12 +3792,10 @@ onion_extend_cpath(origin_circuit_t *circ) } else if (cur_len == 0) { /* picking first node */ const node_t *r = choose_good_entry_server(purpose, state); if (r) { - /* If we're extending to a bridge, use the preferred address - rather than the primary, for potentially extending to an IPv6 - bridge. */ - int use_pref_addr = (r->ri != NULL && - r->ri->purpose == ROUTER_PURPOSE_BRIDGE); - info = extend_info_from_node(r, use_pref_addr); + /* If we're a client, use the preferred address rather than the + primary address, for potentially connecting to an IPv6 OR + port. */ + info = extend_info_from_node(r, server_mode(get_options()) == 0); tor_assert(info); } } else { @@ -3865,10 +3863,11 @@ extend_info_alloc(const char *nickname, const char *digest, /** Allocate and return a new extend_info that can be used to build a * circuit to or through the node node. Use the primary address - * of the node unless for_direct_connect is true, in which case - * the preferred address is used instead. May return NULL if there is - * not enough info about node to extend to it--for example, if - * there is no routerinfo_t or microdesc_t. + * of the node (i.e. its IPv4 address) unless + * for_direct_connect is true, in which case the preferred + * address is used instead. May return NULL if there is not enough + * info about node to extend to it--for example, if there is no + * routerinfo_t or microdesc_t. **/ extend_info_t * extend_info_from_node(const node_t *node, int for_direct_connect) @@ -5607,10 +5606,15 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } } - /* Indicate that we prefer connecting to this bridge over the - protocol that the bridge address indicates. Last bridge - descriptor handled wins. */ - node->ipv6_preferred = tor_addr_family(&bridge->addr) == AF_INET6; + /* Mark bridge as preferably connected to over IPv6 if its IPv6 + address is in a Bridge line and ClientPreferIPv6ORPort is + set. Unless both is true, a potential IPv6 OR port of this + bridge won't get selected. + + XXX ipv6_preferred is never reset (#6757) */ + if (get_options()->ClientPreferIPv6ORPort == 1 && + tor_addr_family(&bridge->addr) == AF_INET6) + node->ipv6_preferred = 1; /* XXXipv6 we lack support for falling back to another address for the same relay, warn the user */ diff --git a/src/or/config.c b/src/or/config.c index a5a8fec507..f21016d48f 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -225,8 +225,10 @@ static config_var_t _option_vars[] = { V(CircuitPriorityHalflife, DOUBLE, "-100.0"), /*negative:'Use default'*/ V(ClientDNSRejectInternalAddresses, BOOL,"1"), V(ClientOnly, BOOL, "0"), + V(ClientPreferIPv6ORPort, BOOL, "0"), V(ClientRejectInternalAddresses, BOOL, "1"), V(ClientTransportPlugin, LINELIST, NULL), + V(ClientUseIPv6, BOOL, "0"), V(ConsensusParams, STRING, NULL), V(ConnLimit, UINT, "1000"), V(ConnDirectionStatistics, BOOL, "0"), diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 9d0247f4ea..1d1bbfc026 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -242,6 +242,8 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_bad_directory = rs->is_bad_directory; node->is_bad_exit = rs->is_bad_exit; node->is_hs_dir = rs->is_hs_dir; + if (options->ClientPreferIPv6ORPort == 1) + node->ipv6_preferred = !tor_addr_is_null(&rs->ipv6_addr); } } SMARTLIST_FOREACH_END(rs); @@ -849,7 +851,13 @@ node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out) { tor_assert(ap_out); - if (node_ipv6_preferred(node)) + /* Cheap implementation of config option ClientUseIPv6 -- simply + don't prefer IPv6 when ClientUseIPv6 is not set. (See #4455 for + more on this subject.) Note that this filter is too strict since + we're hindering not only clients! Erring on the safe side + shouldn't be a problem though. XXX move this check to where + outgoing connections are made? -LN */ + if (get_options()->ClientUseIPv6 == 1 && node_ipv6_preferred(node)) node_get_pref_ipv6_orport(node, ap_out); else node_get_prim_orport(node, ap_out); diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 5558827961..18bf4ae60c 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -42,18 +42,18 @@ int node_get_purpose(const node_t *node); int node_is_me(const node_t *node); int node_exit_policy_rejects_all(const node_t *node); smartlist_t *node_get_all_orports(const node_t *node); -void node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out); -void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out); -void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out); -uint32_t node_get_prim_addr_ipv4h(const node_t *node); int node_allows_single_hop_exits(const node_t *node); const char *node_get_nickname(const node_t *node); const char *node_get_platform(const node_t *node); +uint32_t node_get_prim_addr_ipv4h(const node_t *node); void node_get_address_string(const node_t *node, char *cp, size_t len); long node_get_declared_uptime(const node_t *node); time_t node_get_published_on(const node_t *node); const smartlist_t *node_get_declared_family(const node_t *node); int node_ipv6_preferred(const node_t *node); +void node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out); smartlist_t *nodelist_get_list(void); diff --git a/src/or/or.h b/src/or/or.h index 00a359c533..7536afc690 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -3487,6 +3487,13 @@ typedef struct { * over randomly chosen exits. */ int ClientRejectInternalAddresses; + /** If true, clients may connect over IPv6. XXX we don't really + enforce this -- clients _may_ set up outgoing IPv6 connections + even when this option is not set. */ + int ClientUseIPv6; + /** If true, prefer an IPv6 OR port over an IPv4 one. */ + int ClientPreferIPv6ORPort; + /** The length of time that we think a consensus should be fresh. */ int V3AuthVotingInterval; /** The length of time we think it will take to distribute votes. */ From 7a8366a3eb7081c29cbc0ebf0e9757cbfd71a86d Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 31 Aug 2012 23:02:19 +0200 Subject: [PATCH 2/6] Add IPv6 OR port to microdesc_t and populate it. --- src/or/or.h | 4 ++++ src/or/routerparse.c | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/src/or/or.h b/src/or/or.h index 7536afc690..4bf99e2eaa 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -1960,6 +1960,10 @@ typedef struct microdesc_t { /** As routerinfo_t.onion_pkey */ crypto_pk_t *onion_pkey; + /** As routerinfo_t.ipv6_add */ + tor_addr_t ipv6_addr; + /** As routerinfo_t.ipv6_orport */ + uint16_t ipv6_orport; /** As routerinfo_t.family */ smartlist_t *family; /** Exit policy summary */ diff --git a/src/or/routerparse.c b/src/or/routerparse.c index 60a2eae75f..4eec19f026 100644 --- a/src/or/routerparse.c +++ b/src/or/routerparse.c @@ -522,6 +522,7 @@ static token_rule_t networkstatus_detached_signature_token_table[] = { /** List of tokens recognized in microdescriptors */ static token_rule_t microdesc_token_table[] = { T1_START("onion-key", K_ONION_KEY, NO_ARGS, NEED_KEY_1024), + T0N("a", K_A, GE(1), NO_OBJ ), T01("family", K_FAMILY, ARGS, NO_OBJ ), T01("p", K_P, CONCAT_ARGS, NO_OBJ ), A01("@last-listed", A_LAST_LISTED, CONCAT_ARGS, NO_OBJ ), @@ -4421,6 +4422,14 @@ microdescs_parse_from_string(const char *s, const char *eos, md->onion_pkey = tok->key; tok->key = NULL; + { + smartlist_t *a_lines = find_all_by_keyword(tokens, K_A); + if (a_lines) { + find_single_ipv6_orport(a_lines, &md->ipv6_addr, &md->ipv6_orport); + smartlist_free(a_lines); + } + } + if ((tok = find_opt_by_keyword(tokens, K_FAMILY))) { int i; md->family = smartlist_new(); From 0f45dbefed1ddca4772bf66afb268bc20aefa1d1 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 31 Aug 2012 23:02:19 +0200 Subject: [PATCH 3/6] Use preferred OR for nodes with routerstatus and microdesc too. extend_info_from_node() used to use the primary OR port (i.e. IPv4) unless the node had routerinfo. Now that we have IPv6 addresses in microdescs we may want to use them. Note that this patch changes using r->cache_info.identity_digest into using node->identity. I count on these being well synchronised, or things would break in other ways. Right? --- src/or/circuitbuild.c | 40 ++++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 16 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 34197680b1..2a449b0cc0 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -3872,26 +3872,34 @@ extend_info_alloc(const char *nickname, const char *digest, extend_info_t * extend_info_from_node(const node_t *node, int for_direct_connect) { - if (node->ri) { - const routerinfo_t *r = node->ri; - tor_addr_port_t ap; - if (for_direct_connect) - node_get_pref_orport(node, &ap); - else - node_get_prim_orport(node, &ap); - return extend_info_alloc(r->nickname, r->cache_info.identity_digest, - r->onion_pkey, &ap.addr, ap.port); - } else if (node->rs && node->md) { - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, node->rs->addr); + tor_addr_port_t ap; + + if (node->ri == NULL && (node->rs == NULL || node->md == NULL)) + return NULL; + + if (for_direct_connect) + node_get_pref_orport(node, &ap); + else + node_get_prim_orport(node, &ap); + + log_debug(LD_CIRC, "using %s:%d for %s", + fmt_and_decorate_addr(&ap.addr), ap.port, + node->ri ? node->ri->nickname : node->rs->nickname); + + if (node->ri) + return extend_info_alloc(node->ri->nickname, + node->identity, + node->ri->onion_pkey, + &ap.addr, + ap.port); + else if (node->rs && node->md) return extend_info_alloc(node->rs->nickname, node->identity, node->md->onion_pkey, - &addr, - node->rs->or_port); - } else { + &ap.addr, + ap.port); + else return NULL; - } } /** Release storage held by an extend_info_t struct. */ From 3746215350f4a25c5ec619a8fbe25f3637bb626f Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 31 Aug 2012 23:02:19 +0200 Subject: [PATCH 4/6] Take microdesc IPv6 address into account when setting node->ipv6_preferred. Also, do this only for clients, explicitly. Also, give the flag a value every time we set consensus. We used to touch it only when ClientPreferIPv6ORPort was set, which was wrong. --- src/or/nodelist.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 1d1bbfc026..9ee114bd75 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -206,6 +206,7 @@ nodelist_set_consensus(networkstatus_t *ns) { const or_options_t *options = get_options(); int authdir = authdir_mode_v2(options) || authdir_mode_v3(options); + int client = !server_mode(options); init_nodelist(); if (ns->flavor == FLAV_MICRODESC) @@ -242,8 +243,11 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_bad_directory = rs->is_bad_directory; node->is_bad_exit = rs->is_bad_exit; node->is_hs_dir = rs->is_hs_dir; - if (options->ClientPreferIPv6ORPort == 1) - node->ipv6_preferred = !tor_addr_is_null(&rs->ipv6_addr); + node->ipv6_preferred = 0; + if (client && options->ClientPreferIPv6ORPort == 1 && + (tor_addr_is_null(&rs->ipv6_addr) == 0 || + (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0))) + node->ipv6_preferred = 1; } } SMARTLIST_FOREACH_END(rs); From 8b081231b54f1bb33243078fab510a9a543242a0 Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 31 Aug 2012 23:02:19 +0200 Subject: [PATCH 5/6] Make node_ipv6_preferred() take microdescs into account. Also, make node_get_prim_orport() indicate in its return value whether a valid OR port was copied or not. Maybe we should make it legal to pass ap_out==NULL? --- src/or/nodelist.c | 31 ++++++++++++++++++++++--------- src/or/nodelist.h | 2 +- 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 9ee114bd75..25ed6a61ca 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -821,31 +821,44 @@ node_get_declared_family(const node_t *node) int node_ipv6_preferred(const node_t *node) { + tor_addr_port_t ipv4_addr; node_assert_ok(node); - if (node->ri) - return (!tor_addr_is_null(&node->ri->ipv6_addr) - && (node->ipv6_preferred || node->ri->addr == 0)); - if (node->rs) - return (!tor_addr_is_null(&node->rs->ipv6_addr) - && (node->ipv6_preferred || node->rs->addr == 0)); + + if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr)) { + if (node->ri) + return !tor_addr_is_null(&node->ri->ipv6_addr); + if (node->md) + return !tor_addr_is_null(&node->md->ipv6_addr); + if (node->rs) + return !tor_addr_is_null(&node->rs->ipv6_addr); + } return 0; } /** Copy the primary (IPv4) OR port (IP address and TCP port) for - * node into *ap_out. */ -void + * node into *ap_out. Return 0 if a valid address and + * port was copied, else return non-zero.*/ +int node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out) { node_assert_ok(node); tor_assert(ap_out); if (node->ri) { + if (node->ri->addr == 0 || node->ri->or_port == 0) + return -1; tor_addr_from_ipv4h(&ap_out->addr, node->ri->addr); ap_out->port = node->ri->or_port; - } else if (node->rs) { + return 0; + } + if (node->rs) { + if (node->rs->addr == 0 || node->rs->or_port == 0) + return -1; tor_addr_from_ipv4h(&ap_out->addr, node->rs->addr); ap_out->port = node->rs->or_port; + return 0; } + return -1; } /** Copy the preferred OR port (IP address and TCP port) for diff --git a/src/or/nodelist.h b/src/or/nodelist.h index 18bf4ae60c..fb65fa5483 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -51,7 +51,7 @@ long node_get_declared_uptime(const node_t *node); time_t node_get_published_on(const node_t *node); const smartlist_t *node_get_declared_family(const node_t *node); int node_ipv6_preferred(const node_t *node); -void node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out); +int node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out); void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out); void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out); From d827a5495a9d05976f5610655f7e4b5b160a2dac Mon Sep 17 00:00:00 2001 From: Linus Nordberg Date: Fri, 31 Aug 2012 23:02:19 +0200 Subject: [PATCH 6/6] Take microdesc into account when deciding about preferred OR port. --- src/or/nodelist.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 25ed6a61ca..e35039bdb8 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -888,9 +888,17 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out) node_assert_ok(node); tor_assert(ap_out); + /* We prefer the microdesc over a potential routerstatus here. They + are not being synchronised atm so there might be a chance that + they differ at some point, f.ex. when flipping + UseMicrodescriptors? -LN */ + if (node->ri) { tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr); ap_out->port = node->ri->ipv6_orport; + } else if (node->md) { + tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr); + ap_out->port = node->md->ipv6_orport; } else if (node->rs) { tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); ap_out->port = node->rs->ipv6_orport;