From 4460feaf2850ef0fb027a2d01786a5bbaee056dc Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Tue, 22 Dec 2015 10:42:09 +1100 Subject: [PATCH 01/15] Fix *_get_all_orports to use ipv6_orport node_get_all_orports and router_get_all_orports incorrectly used or_port with IPv6 addresses. They now use ipv6_orport. Also refactor and remove duplicated code. --- src/common/address.c | 53 ++++++++++++++++++++++++++++++ src/common/address.h | 21 ++++++++++++ src/or/nodelist.c | 76 ++++++++++++++++++++++++++++++++++---------- src/or/router.c | 26 ++++----------- 4 files changed, 140 insertions(+), 36 deletions(-) diff --git a/src/common/address.c b/src/common/address.c index 69a80986ed..19e9fddd08 100644 --- a/src/common/address.c +++ b/src/common/address.c @@ -908,6 +908,59 @@ tor_addr_is_loopback(const tor_addr_t *addr) } } +/* Is addr valid? + * Checks that addr is non-NULL and not tor_addr_is_null(). + * If for_listening is true, IPv4 addr 0.0.0.0 is allowed. + * It means "bind to all addresses on the local machine". */ +int +tor_addr_is_valid(const tor_addr_t *addr, int for_listening) +{ + /* NULL addresses are invalid regardless of for_listening */ + if (addr == NULL) { + return 0; + } + + /* Only allow IPv4 0.0.0.0 for_listening. */ + if (for_listening && addr->family == AF_INET + && tor_addr_to_ipv4h(addr) == 0) { + return 1; + } + + /* Otherwise, the address is valid if it's not tor_addr_is_null() */ + return !tor_addr_is_null(addr); +} + +/* Is the network-order IPv4 address v4n_addr valid? + * Checks that addr is not zero. + * Except if for_listening is true, where IPv4 addr 0.0.0.0 is allowed. */ +int +tor_addr_is_valid_ipv4n(uint32_t v4n_addr, int for_listening) +{ + /* Any IPv4 address is valid with for_listening. */ + if (for_listening) { + return 1; + } + + /* Otherwise, zero addresses are invalid. */ + return v4n_addr != 0; +} + +/* Is port valid? + * Checks that port is not 0. + * Except if for_listening is true, where port 0 is allowed. + * It means "OS chooses a port". */ +int +tor_port_is_valid(uint16_t port, int for_listening) +{ + /* Any port value is valid with for_listening. */ + if (for_listening) { + return 1; + } + + /* Otherwise, zero ports are invalid. */ + return port != 0; +} + /** Set dest to equal the IPv4 address in v4addr (given in * network order). */ void diff --git a/src/common/address.h b/src/common/address.h index 684ba65c4f..918b024ea6 100644 --- a/src/common/address.h +++ b/src/common/address.h @@ -267,6 +267,27 @@ void tor_addr_from_in6(tor_addr_t *dest, const struct in6_addr *in6); int tor_addr_is_null(const tor_addr_t *addr); int tor_addr_is_loopback(const tor_addr_t *addr); +int tor_addr_is_valid(const tor_addr_t *addr, int for_listening); +int tor_addr_is_valid_ipv4n(uint32_t v4n_addr, int for_listening); +#define tor_addr_is_valid_ipv4h(v4h_addr, for_listening) \ + tor_addr_is_valid_ipv4n(htonl(v4h_addr), (for_listening)) +int tor_port_is_valid(uint16_t port, int for_listening); +/* Are addr and port both valid? */ +#define tor_addr_port_is_valid(addr, port, for_listening) \ + (tor_addr_is_valid((addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) +/* Are ap->addr and ap->port both valid? */ +#define tor_addr_port_is_valid_ap(ap, for_listening) \ + tor_addr_port_is_valid(&(ap)->addr, (ap)->port, (for_listening)) +/* Are the network-order v4addr and port both valid? */ +#define tor_addr_port_is_valid_ipv4n(v4n_addr, port, for_listening) \ + (tor_addr_is_valid_ipv4n((v4n_addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) +/* Are the host-order v4addr and port both valid? */ +#define tor_addr_port_is_valid_ipv4h(v4h_addr, port, for_listening) \ + (tor_addr_is_valid_ipv4h((v4h_addr), (for_listening)) && \ + tor_port_is_valid((port), (for_listening))) + int tor_addr_port_split(int severity, const char *addrport, char **address_out, uint16_t *port_out); diff --git a/src/or/nodelist.c b/src/or/nodelist.c index fc27207851..a1d99e9899 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -754,6 +754,40 @@ node_exit_policy_is_exact(const node_t *node, sa_family_t family) return 1; } +/* Check if the "addr" and port_field fields from r are a valid non-listening + * address/port. If so, set valid to true and add a newly allocated + * tor_addr_port_t containing "addr" and port_field to sl. + * "addr" is an IPv4 host-order address and port_field is a uint16_t. + * r is typically a routerinfo_t or routerstatus_t. + */ +#define SL_ADD_NEW_IPV4_AP(r, port_field, sl, valid) \ + STMT_BEGIN \ + if (tor_addr_port_is_valid_ipv4h((r)->addr, (r)->port_field, 0)) { \ + valid = 1; \ + tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); \ + tor_addr_from_ipv4h(&ap->addr, (r)->addr); \ + ap->port = (r)->port_field; \ + smartlist_add((sl), ap); \ + } \ + STMT_END + +/* Check if the "addr" and port_field fields from r are a valid non-listening + * address/port. If so, set valid to true and add a newly allocated + * tor_addr_port_t containing "addr" and port_field to sl. + * "addr" is a tor_addr_t and port_field is a uint16_t. + * r is typically a routerinfo_t or routerstatus_t. + */ +#define SL_ADD_NEW_IPV6_AP(r, port_field, sl, valid) \ + STMT_BEGIN \ + if (tor_addr_port_is_valid(&(r)->ipv6_addr, (r)->port_field, 0)) { \ + valid = 1; \ + tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); \ + tor_addr_copy(&ap->addr, &(r)->ipv6_addr); \ + ap->port = (r)->port_field; \ + smartlist_add((sl), ap); \ + } \ + STMT_END + /** Return list of tor_addr_port_t with all OR ports (in the sense IP * addr + TCP port) for node. Caller must free all elements * using tor_free() and free the list using smartlist_free(). @@ -766,30 +800,38 @@ smartlist_t * node_get_all_orports(const node_t *node) { smartlist_t *sl = smartlist_new(); + int valid = 0; + /* Find a valid IPv4 address and port */ if (node->ri != NULL) { - if (node->ri->addr != 0) { - tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); - tor_addr_from_ipv4h(&ap->addr, node->ri->addr); - ap->port = node->ri->or_port; - smartlist_add(sl, ap); - } - if (!tor_addr_is_null(&node->ri->ipv6_addr)) { - tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); - tor_addr_copy(&ap->addr, &node->ri->ipv6_addr); - ap->port = node->ri->or_port; - smartlist_add(sl, ap); - } - } else if (node->rs != NULL) { - tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); - tor_addr_from_ipv4h(&ap->addr, node->rs->addr); - ap->port = node->rs->or_port; - smartlist_add(sl, ap); + SL_ADD_NEW_IPV4_AP(node->ri, or_port, sl, valid); + } + + /* If we didn't find a valid address/port in the ri, try the rs */ + if (!valid && node->rs != NULL) { + SL_ADD_NEW_IPV4_AP(node->rs, or_port, sl, valid); + } + + /* Find a valid IPv6 address and port */ + valid = 0; + if (node->ri != NULL) { + SL_ADD_NEW_IPV6_AP(node->ri, ipv6_orport, sl, valid); + } + + if (!valid && node->rs != NULL) { + SL_ADD_NEW_IPV6_AP(node->rs, ipv6_orport, sl, valid); + } + + if (!valid && node->md != NULL) { + SL_ADD_NEW_IPV6_AP(node->md, ipv6_orport, sl, valid); } return sl; } +#undef SL_ADD_NEW_IPV4_AP +#undef SL_ADD_NEW_IPV6_AP + /** Wrapper around node_get_prim_orport for backward compatibility. */ void diff --git a/src/or/router.c b/src/or/router.c index c35f629f30..ba32d77fd1 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -3350,28 +3350,16 @@ router_free_all(void) /** Return a smartlist of tor_addr_port_t's with all the OR ports of ri. Note that freeing of the items in the list as well as - the smartlist itself is the callers responsibility. - - XXX duplicating code from node_get_all_orports(). */ + the smartlist itself is the callers responsibility. */ smartlist_t * router_get_all_orports(const routerinfo_t *ri) { - smartlist_t *sl = smartlist_new(); tor_assert(ri); - - if (ri->addr != 0) { - tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); - tor_addr_from_ipv4h(&ap->addr, ri->addr); - ap->port = ri->or_port; - smartlist_add(sl, ap); - } - if (!tor_addr_is_null(&ri->ipv6_addr)) { - tor_addr_port_t *ap = tor_malloc(sizeof(tor_addr_port_t)); - tor_addr_copy(&ap->addr, &ri->ipv6_addr); - ap->port = ri->or_port; - smartlist_add(sl, ap); - } - - return sl; + node_t fake_node; + memset(&fake_node, 0, sizeof(fake_node)); + /* we don't modify ri, fake_node is passed as a const node_t * + */ + fake_node.ri = (routerinfo_t *)ri; + return node_get_all_orports(&fake_node); } From 2d33d192fc4dd0da2a2e038dd87b277f8e9b90de Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Mon, 14 Dec 2015 17:23:10 +1100 Subject: [PATCH 02/15] Add ClientUseIPv4 and ClientPreferIPv6DirPort torrc options ClientUseIPv4 0 tells tor to avoid IPv4 client connections. ClientPreferIPv6DirPort 1 tells tor to prefer IPv6 directory connections. Refactor policy for IPv4/IPv6 preferences. Fix a bug where node->ipv6_preferred could become stale if ClientPreferIPv6ORPort was changed after the consensus was loaded. Update documentation, existing code, add unit tests. --- changes/feature17840 | 9 + doc/tor.1.txt | 33 +- src/or/circuitbuild.c | 14 +- src/or/config.c | 51 ++- src/or/connection.c | 2 + src/or/directory.c | 8 +- src/or/entrynodes.c | 8 +- src/or/nodelist.c | 265 ++++++++++--- src/or/nodelist.h | 11 +- src/or/or.h | 21 +- src/or/policies.c | 743 +++++++++++++++++++++++++++++++++++-- src/or/policies.h | 62 +++- src/test/test_entrynodes.c | 97 +++++ src/test/test_policy.c | 447 ++++++++++++++++++++++ 14 files changed, 1650 insertions(+), 121 deletions(-) create mode 100644 changes/feature17840 diff --git a/changes/feature17840 b/changes/feature17840 new file mode 100644 index 0000000000..b8b3b7f5b5 --- /dev/null +++ b/changes/feature17840 @@ -0,0 +1,9 @@ + o Minor feature (IPv6): + - Add ClientUseIPv4, which is set to 1 by default. If set to 0, tor + avoids using IPv4 for client OR and directory connections. + - Add ClientPreferIPv6DirPort, which is set to 0 by default. If set + to 1, tor prefers IPv6 directory addresses. + - Try harder to fulfil IP version restrictions ClientUseIPv4 0 and + ClientUseIPv6 0; and the preferences ClientPreferIPv6ORPort and + ClientPreferIPv6DirPort. + Closes ticket 17840; patch by "teor". diff --git a/doc/tor.1.txt b/doc/tor.1.txt index f173a97aa3..26abef1b91 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -367,6 +367,7 @@ GENERAL OPTIONS authorities. By default, the directory authorities are also FallbackDirs. Specifying a FallbackDir replaces Tor's default hard-coded FallbackDirs (if any). + (See the **DirAuthority** entry for an explanation of each flag.) [[UseDefaultFallbackDirs]] **UseDefaultFallbackDirs** **0**|**1**:: Use Tor's default hard-coded FallbackDirs (if any). (When a @@ -390,6 +391,10 @@ GENERAL OPTIONS if an "ipv6=__address__:__orport__" flag is present, then the directory authority is listening for IPv6 connections on the indicated IPv6 address and OR Port. + + + + Tor will contact the authority at __address__:__port__ (the DirPort) to + download directory documents. If an IPv6 address is supplied, Tor will + also download directory documents at the IPv6 address on the DirPort. + + If no **DirAuthority** line is given, Tor will use the default directory authorities. NOTE: this option is intended for setting up a private Tor @@ -1483,17 +1488,31 @@ The following options are useful only for clients (that is, if If no defaults are available there, these options default to 20, .80, .60, and 100, respectively. +[[ClientUseIPv4]] **ClientUseIPv4** **0**|**1**:: + If this option is set to 0, Tor will avoid connecting to directory servers + and entry nodes over IPv4. Note that clients with an IPv4 + address in a **Bridge**, proxy, or pluggable transport line will try + connecting over IPv4 even if **ClientUseIPv4** is set to 0. (Default: 1) + [[ClientUseIPv6]] **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** line will try connecting over IPv6 even if - **ClientUseIPv6** is set to 0. (Default: 0) + If this option is set to 1, Tor might connect to directory servers or + entry nodes over IPv6. Note that clients configured with an IPv6 address + in a **Bridge**, proxy, or pluggable transport line will try connecting + over IPv6 even if **ClientUseIPv6** is set to 0. (Default: 0) + +[[ClientPreferIPv6DirPort]] **ClientPreferIPv6DirPort** **0**|**1**:: + If this option is set to 1, Tor prefers a directory port with an IPv6 + address over one with IPv4, for direct connections, if a given directory + server has both. (Tor also prefers an IPv6 DirPort if IPv4Client is set to + 0.) Other things may influence the choice. This option breaks a tie to the + favor of IPv6. (Default: 0) [[ClientPreferIPv6ORPort]] **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) + address over one with IPv4 if a given entry node has both. (Tor also + prefers an IPv6 ORPort if IPv4Client is set to 0.) Other things may + influence the choice. This option breaks a tie to the favor of IPv6. + (Default: 0) [[PathsNeededToBuildCircuits]] **PathsNeededToBuildCircuits** __NUM__:: Tor clients don't build circuits for user traffic until they know diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index 719d27caa9..d44fcd74ef 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2161,14 +2161,12 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) * family. */ nodelist_add_node_and_family(excluded, node); } - if (firewall_is_fascist_or()) { - /* Exclude all ORs that we can't reach through our firewall */ - smartlist_t *nodes = nodelist_get_list(); - SMARTLIST_FOREACH(nodes, const node_t *, node, { - if (!fascist_firewall_allows_node(node)) - smartlist_add(excluded, (void*)node); - }); - } + /* Exclude all ORs that we can't reach through our firewall */ + smartlist_t *nodes = nodelist_get_list(); + SMARTLIST_FOREACH(nodes, const node_t *, node, { + if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) + smartlist_add(excluded, (void*)node); + }); /* and exclude current entry guards and their families, * unless we're in a test network, and excluding guards * would exclude all nodes (i.e. we're in an incredibly small tor network, diff --git a/src/or/config.c b/src/or/config.c index 9ec47d2459..d676c6e29d 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -191,9 +191,11 @@ static config_var_t option_vars_[] = { V(ClientDNSRejectInternalAddresses, BOOL,"1"), V(ClientOnly, BOOL, "0"), V(ClientPreferIPv6ORPort, BOOL, "0"), + V(ClientPreferIPv6DirPort, BOOL, "0"), V(ClientRejectInternalAddresses, BOOL, "1"), V(ClientTransportPlugin, LINELIST, NULL), V(ClientUseIPv6, BOOL, "0"), + V(ClientUseIPv4, BOOL, "1"), V(ConsensusParams, STRING, NULL), V(ConnLimit, UINT, "1000"), V(ConnDirectionStatistics, BOOL, "0"), @@ -3071,6 +3073,9 @@ options_validate(or_options_t *old_options, or_options_t *options, } } + /* Terminate Reachable*Addresses with reject *, but check if it has an + * IPv6 entry on the way through */ + int reachable_knows_ipv6 = 0; for (i=0; i<3; i++) { config_line_t **linep = (i==0) ? &options->ReachableAddresses : @@ -3080,7 +3085,19 @@ options_validate(or_options_t *old_options, or_options_t *options, continue; /* We need to end with a reject *:*, not an implicit accept *:* */ for (;;) { - if (!strcmp((*linep)->value, "reject *:*")) /* already there */ + /* Check if the policy has an IPv6 entry, or uses IPv4-specific + * policies (and therefore we assume it's aware of IPv6). */ + if (!strcmpstart((*linep)->value, "accept6") || + !strcmpstart((*linep)->value, "reject6") || + !strstr((*linep)->value, "*6") || + strchr((*linep)->value, '[') || + !strcmpstart((*linep)->value, "accept4") || + !strcmpstart((*linep)->value, "reject4") || + !strstr((*linep)->value, "*4")) + reachable_knows_ipv6 = 1; + /* already has a reject all */ + if (!strcmp((*linep)->value, "reject *:*") || + !strcmp((*linep)->value, "reject *")) break; linep = &((*linep)->next); if (!*linep) { @@ -3095,13 +3112,41 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - if ((options->ReachableAddresses || + if (options->ClientUseIPv6 && + (options->ReachableAddresses || options->ReachableORAddresses || options->ReachableDirAddresses) && + !reachable_knows_ipv6) + log_warn(LD_CONFIG, "You have set ClientUseIPv6 1 and at least one of " + "ReachableAddresses, ReachableORAddresses, or " + "ReachableDirAddresses, but without any IPv6-specific rules. " + "Tor won't connect to any IPv6 addresses, unless a rule accepts " + "them. (Use 'accept6 *:*' or 'reject6 *:*' as the last rule to " + "disable this warning.)"); + + if ((options->ReachableAddresses || + options->ReachableORAddresses || + options->ReachableDirAddresses || + options->ClientUseIPv4 == 0) && server_mode(options)) REJECT("Servers must be able to freely connect to the rest " "of the Internet, so they must not set Reachable*Addresses " - "or FascistFirewall."); + "or FascistFirewall or FirewallPorts or ClientUseIPv4 0."); + + /* We check if Reachable*Addresses blocks all addresses in + * parse_reachable_addresses(). */ + if (options->ClientUseIPv4 == 0 && options->ClientUseIPv6 == 0) + REJECT("Tor cannot connect to the Internet if ClientUseIPv4 is 0 and " + "ClientUseIPv6 is 0. Please set at least one of these options " + "to 1."); + + if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6ORPort == 1) + log_warn(LD_CONFIG, "ClientPreferIPv6ORPort 1 is ignored unless " + "ClientUseIPv6 is also 1."); + + if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6DirPort == 1) + log_warn(LD_CONFIG, "ClientPreferIPv6DirPort 1 is ignored unless " + "ClientUseIPv6 is also 1."); if (options->UseBridges && server_mode(options)) diff --git a/src/or/connection.c b/src/or/connection.c index b4cd4cdddb..9765a8ea91 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -37,6 +37,7 @@ #include "ext_orport.h" #include "geoip.h" #include "main.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -44,6 +45,7 @@ #include "rendcommon.h" #include "rephist.h" #include "router.h" +#include "routerlist.h" #include "transports.h" #include "routerparse.h" #include "transports.h" diff --git a/src/or/directory.c b/src/or/directory.c index 8370095e92..d5531d88da 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -313,7 +313,6 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, SMARTLIST_FOREACH_BEGIN(dirservers, dir_server_t *, ds) { routerstatus_t *rs = &(ds->fake_status); size_t upload_len = payload_len; - tor_addr_t ds_addr; if ((type & ds->type) == 0) continue; @@ -344,11 +343,12 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose, log_info(LD_DIR, "Uploading an extrainfo too (length %d)", (int) extrainfo_len); } - tor_addr_from_ipv4h(&ds_addr, ds->addr); if (purpose_needs_anonymity(dir_purpose, router_purpose)) { indirection = DIRIND_ANONYMOUS; - } else if (!fascist_firewall_allows_address_dir(&ds_addr,ds->dir_port)) { - if (fascist_firewall_allows_address_or(&ds_addr,ds->or_port)) + } else if (!fascist_firewall_allows_dir_server(ds, + FIREWALL_DIR_CONNECTION, + 0)) { + if (fascist_firewall_allows_dir_server(ds, FIREWALL_OR_CONNECTION, 0)) indirection = DIRIND_ONEHOP; else indirection = DIRIND_ANONYMOUS; diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index bf71fc30c0..e358e92ccd 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -268,7 +268,7 @@ entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags, *msg = "not fast/stable"; return NULL; } - if (!fascist_firewall_allows_node(node)) { + if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) { *msg = "unreachable by config"; return NULL; } @@ -918,7 +918,8 @@ entry_guards_set_from_config(const or_options_t *options) } else if (routerset_contains_node(options->ExcludeNodes, node)) { SMARTLIST_DEL_CURRENT(entry_nodes, node); continue; - } else if (!fascist_firewall_allows_node(node)) { + } else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, + 0)) { SMARTLIST_DEL_CURRENT(entry_nodes, node); continue; } else if (! node->is_possible_guard) { @@ -2178,7 +2179,8 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now) !options->UpdateBridgesFromAuthority, !num_bridge_auths); if (ask_bridge_directly && - !fascist_firewall_allows_address_or(&bridge->addr, bridge->port)) { + !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, + FIREWALL_OR_CONNECTION, 0)) { log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our " "firewall policy. %s.", fmt_addrport(&bridge->addr, bridge->port), diff --git a/src/or/nodelist.c b/src/or/nodelist.c index a1d99e9899..7ca1146e86 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -214,6 +214,76 @@ nodelist_add_microdesc(microdesc_t *md) return node; } +/** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and + * ClientPreferIPv6DirPort? + * If we're unsure, return -1, otherwise, return 1 for IPv6 and 0 for IPv4. + */ +static int +nodelist_prefer_ipv6(const or_options_t *options) +{ + /* + Cheap implementation of config options ClientUseIPv4 & ClientUseIPv6 -- + If we're a server, use IPv4. + If we're a client running with bridges, use IPv6. + Otherwise, use IPv6 if we can and it's preferred, or if IPv4 is disabled. + See #4455 and #17840 for more on this subject. + */ + + /* Servers prefer IPv4 */ + if (server_mode(options)) { + return 0; + } + + /* Bridge clients prefer IPv6 */ + if (options->UseBridges) { + return 1; + } + + if (!options->ClientUseIPv4) { + return 1; + } + + return -1; +} + +/** Do we prefer to connect to IPv6 ORPorts? + */ +int +nodelist_prefer_ipv6_orport(const or_options_t *options) +{ + int pref_ipv6 = nodelist_prefer_ipv6(options); + + if (pref_ipv6 >= 0) { + return pref_ipv6; + } + + /* We prefer IPv6 ORPorts if the option is set */ + if (options->ClientUseIPv6 && options->ClientPreferIPv6ORPort) { + return 1; + } + + return 0; +} + +/** Do we prefer to connect to IPv6 DirPorts? + */ +int +nodelist_prefer_ipv6_dirport(const or_options_t *options) +{ + int pref_ipv6 = nodelist_prefer_ipv6(options); + + if (pref_ipv6 >= 0) { + return pref_ipv6; + } + + /* We prefer IPv6 DirPorts if the option is set */ + if (options->ClientUseIPv6 && options->ClientPreferIPv6DirPort) { + return 1; + } + + return 0; +} + /** Tell the nodelist that the current usable consensus is ns. * This makes the nodelist change all of the routerstatus entries for * the nodes, drop nodes that no longer have enough info to get used, @@ -224,7 +294,6 @@ nodelist_set_consensus(networkstatus_t *ns) { const or_options_t *options = get_options(); int authdir = authdir_mode_v3(options); - int client = !server_mode(options); init_nodelist(); if (ns->flavor == FLAV_MICRODESC) @@ -261,7 +330,7 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_bad_exit = rs->is_bad_exit; node->is_hs_dir = rs->is_hs_dir; node->ipv6_preferred = 0; - if (client && options->ClientPreferIPv6ORPort == 1 && + if (nodelist_prefer_ipv6_orport(options) && (tor_addr_is_null(&rs->ipv6_addr) == 0 || (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0))) node->ipv6_preferred = 1; @@ -925,30 +994,60 @@ node_get_declared_family(const node_t *node) return NULL; } +/* Does this node have a valid IPv6 address? */ +static int +node_has_ipv6_addr(const node_t *node) +{ + 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; +} + /** Return 1 if we prefer the IPv6 address and OR TCP port of * node, else 0. * - * We prefer the IPv6 address if the router has an IPv6 address and + * We prefer the IPv6 address if the router has an IPv6 address, + * and we can use IPv6 addresses, and: * i) the node_t says that it prefers IPv6 * or - * ii) the router has no IPv4 address. */ + * ii) the router has no IPv4 OR address. + * or + * iii) our preference is for IPv6 addresses. + * (This extra step is needed in case our preferences have changed since + * node->ipv6_preferred was set at the time the consensus was loaded.) + */ int -node_ipv6_preferred(const node_t *node) +node_ipv6_or_preferred(const node_t *node) { + const or_options_t *options = get_options(); tor_addr_port_t ipv4_addr; node_assert_ok(node); - 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); + if (!options->ClientUseIPv6) { + return 0; + } else if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr) + || nodelist_prefer_ipv6_orport(get_options())) { + return node_has_ipv6_addr(node); } return 0; } +#define RETURN_IPV4_AP(r, port_field, ap_out) \ + STMT_BEGIN \ + if (r) { \ + if ((r)->addr == 0 || (r)->port_field == 0) \ + return -1; \ + tor_addr_from_ipv4h(&(ap_out)->addr, (r)->addr); \ + (ap_out)->port = (r)->port_field; \ + return 0; \ + } \ + STMT_END + /** Copy the primary (IPv4) OR port (IP address and TCP port) for * node into *ap_out. Return 0 if a valid address and * port was copied, else return non-zero.*/ @@ -958,20 +1057,10 @@ 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; - 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_IPV4_AP(node->ri, or_port, ap_out); + RETURN_IPV4_AP(node->rs, or_port, ap_out); + /* Microdescriptors only have an IPv6 address */ + return -1; } @@ -980,21 +1069,12 @@ 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) { - const or_options_t *options = get_options(); tor_assert(ap_out); - /* Cheap implementation of config option ClientUseIPv6 -- simply - don't prefer IPv6 when ClientUseIPv6 is not set and we're not a - client running with bridges. 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 ((options->ClientUseIPv6 || options->UseBridges) && - node_ipv6_preferred(node)) { + if (node_ipv6_or_preferred(node)) { node_get_pref_ipv6_orport(node, ap_out); } else { + /* the primary ORPort is always on IPv4 */ node_get_prim_orport(node, ap_out); } } @@ -1007,20 +1087,113 @@ 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 */ + /* Prefer routerstatus over microdesc for consistency with the + * fascist_firewall_* functions. Also check if the address or port are valid, + * and try another alternative if they are not. */ - if (node->ri) { + if (node->ri && node->ri->ipv6_orport + && !tor_addr_is_null(&node->ri->ipv6_addr)) { 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) { + } else if (node->rs && node->rs->ipv6_orport + && !tor_addr_is_null(&node->rs->ipv6_addr)) { tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); ap_out->port = node->rs->ipv6_orport; + } else if (node->md && node->md->ipv6_orport + && !tor_addr_is_null(&node->md->ipv6_addr)) { + tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr); + ap_out->port = node->md->ipv6_orport; + } else { + tor_addr_make_null(&ap_out->addr, AF_INET6); + ap_out->port = 0; + } +} + +/** Return 1 if we prefer the IPv6 address and Dir TCP port of + * node, else 0. + * + * We prefer the IPv6 address if the router has an IPv6 address, + * and we can use IPv6 addresses, and: + * i) the node_t says that it prefers IPv6 + * or + * ii) the router has no IPv4 Dir address. + * or + * iii) our preference is for IPv6 addresses. + * (This extra step is needed in case our preferences have changed since + * node->ipv6_preferred was set at the time the consensus was loaded.) + */ +int +node_ipv6_dir_preferred(const node_t *node) +{ + const or_options_t *options = get_options(); + tor_addr_port_t ipv4_addr; + node_assert_ok(node); + + if (!options->ClientUseIPv6) { + return 0; + } else if (node->ipv6_preferred || node_get_prim_dirport(node, &ipv4_addr) + || nodelist_prefer_ipv6_dirport(get_options())) { + return node_has_ipv6_addr(node); + } + return 0; +} + +/** Copy the primary (IPv4) Dir port (IP address and TCP port) for + * node into *ap_out. Return 0 if a valid address and + * port was copied, else return non-zero.*/ +int +node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out) +{ + node_assert_ok(node); + tor_assert(ap_out); + + RETURN_IPV4_AP(node->ri, dir_port, ap_out); + RETURN_IPV4_AP(node->rs, dir_port, ap_out); + /* Microdescriptors only have an IPv6 address */ + + return -1; +} + +#undef RETURN_IPV4_AP + +/** Copy the preferred Dir port (IP address and TCP port) for + * node into *ap_out. */ +void +node_get_pref_dirport(const node_t *node, tor_addr_port_t *ap_out) +{ + tor_assert(ap_out); + + if (node_ipv6_dir_preferred(node)) { + node_get_pref_ipv6_dirport(node, ap_out); + } else { + /* the primary DirPort is always on IPv4 */ + node_get_prim_dirport(node, ap_out); + } +} + +/** Copy the preferred IPv6 Dir port (IP address and TCP port) for + * node into *ap_out. */ +void +node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out) +{ + node_assert_ok(node); + tor_assert(ap_out); + + /* Check if the address or port are valid, and try another alternative if + * they are not. Note that microdescriptors have no dir_port. */ + + /* Assume IPv4 and IPv6 dirports are the same */ + if (node->ri && node->ri->dir_port + && !tor_addr_is_null(&node->ri->ipv6_addr)) { + tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr); + ap_out->port = node->ri->dir_port; + } else if (node->rs && node->rs->dir_port + && !tor_addr_is_null(&node->rs->ipv6_addr)) { + tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); + ap_out->port = node->rs->dir_port; + } else { + tor_addr_make_null(&ap_out->addr, AF_INET6); + ap_out->port = 0; } } diff --git a/src/or/nodelist.h b/src/or/nodelist.h index a131e0dd4e..fa1f22e630 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -21,6 +21,8 @@ MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); const node_t *node_get_by_hex_id(const char *identity_digest); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); +int nodelist_prefer_ipv6_orport(const or_options_t *options); +int nodelist_prefer_ipv6_dirport(const or_options_t *options); void nodelist_set_consensus(networkstatus_t *ns); void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); @@ -55,10 +57,17 @@ 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); + +/* Deprecated - use node_ipv6_or_preferred or node_ipv6_dir_preferred */ +#define node_ipv6_preferred(node) node_ipv6_or_preferred(node) +int node_ipv6_or_preferred(const node_t *node); 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); +int node_ipv6_dir_preferred(const node_t *node); +int node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_dirport(const node_t *node, tor_addr_port_t *ap_out); +void node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out); int node_has_curve25519_onion_key(const node_t *node); MOCK_DECL(smartlist_t *, nodelist_get_list, (void)); diff --git a/src/or/or.h b/src/or/or.h index e621fe9708..b1765d1d57 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -2400,7 +2400,8 @@ typedef struct node_t { /* Local info: derived. */ - /** True if the IPv6 OR port is preferred over the IPv4 OR port. */ + /** True if the IPv6 OR port is preferred over the IPv4 OR port. + * XX/teor - can this become out of date if the torrc changes? */ unsigned int ipv6_preferred:1; /** According to the geoip db what country is this router in? */ @@ -4061,12 +4062,20 @@ 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. */ + /** If true, clients may connect over IPv4. If false, they will avoid + * connecting over IPv4. We enforce this for OR and Dir connections. */ + int ClientUseIPv4; + /** If true, clients may connect over IPv6. If false, they will avoid + * connecting over IPv4. We enforce this for OR and Dir connections. */ int ClientUseIPv6; - /** If true, prefer an IPv6 OR port over an IPv4 one. */ + /** If true, prefer an IPv6 OR port over an IPv4 one for entry node + * connections. Use nodelist_prefer_ipv6_orport() instead of accessing + * this value directly. */ int ClientPreferIPv6ORPort; + /** If true, prefer an IPv6 directory port over an IPv4 one for direct + * directory connections. Use nodelist_prefer_ipv6_dirport() instead of + * accessing this value directly. */ + int ClientPreferIPv6DirPort; /** The length of time that we think a consensus should be fresh. */ int V3AuthVotingInterval; @@ -5125,6 +5134,8 @@ typedef struct dir_server_t { char *description; char *nickname; char *address; /**< Hostname. */ + /* XX/teor - why do we duplicate the address and port fields here and in + * fake_status? Surely we could just use fake_status (#17867). */ tor_addr_t ipv6_addr; /**< IPv6 address if present; AF_UNSPEC if not */ uint32_t addr; /**< IPv4 address. */ uint16_t dir_port; /**< Directory port. */ diff --git a/src/or/policies.c b/src/or/policies.c index c9bce1b234..2a97df7cac 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -13,6 +13,7 @@ #include "or.h" #include "config.h" #include "dirserv.h" +#include "networkstatus.h" #include "nodelist.h" #include "policies.h" #include "router.h" @@ -270,16 +271,21 @@ parse_reachable_addresses(void) "Error parsing ReachableDirAddresses entry; ignoring."); ret = -1; } - return ret; -} -/** Return true iff the firewall options might block any address:port - * combination. - */ -int -firewall_is_fascist_or(void) -{ - return reachable_or_addr_policy != NULL; + /* XX/teor - we ignore ReachableAddresses for bridge clients and relays */ + if (!options->UseBridges || server_mode(options)) { + if ((reachable_or_addr_policy + && policy_is_reject_star(reachable_or_addr_policy, AF_UNSPEC)) + || (reachable_dir_addr_policy + && policy_is_reject_star(reachable_dir_addr_policy, AF_UNSPEC))) { + log_warn(LD_CONFIG, "Tor cannot connect to the Internet if " + "ReachableAddresses, ReachableORAddresses, or " + "ReachableDirAddresses reject all addresses. Please accept " + "some addresses in these options."); + } + } + + return ret; } /** Return true iff policy (possibly NULL) will allow a @@ -317,49 +323,708 @@ addr_policy_permits_address(uint32_t addr, uint16_t port, return addr_policy_permits_tor_addr(&a, port, policy); } -/** Return true iff we think our firewall will let us make an OR connection to - * addr:port. */ -int -fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port) +/** Return true iff we think our firewall will let us make a connection to + * addr:port. + * + * If UseBridges is set, or we are configured as a server, ignore the + * following address family preferences. + * Otherwise: + * - return false for all IPv4 addresses: + * - if ClientUseIPv4 is 0, or + * if pref_only and pref_ipv6 are both true; + * - return false for all IPv6 addresses: + * - if ClientUseIPv6 is 0, or + * - if pref_only is true and pref_ipv6 is false. + * + * Return false if addr is NULL or tor_addr_is_null(), or if port is 0. */ +STATIC int +fascist_firewall_allows_address(const tor_addr_t *addr, + uint16_t port, + smartlist_t *firewall_policy, + int pref_only, int pref_ipv6) { + const or_options_t *options = get_options(); + + if (!addr || tor_addr_is_null(addr) || !port) { + return 0; + } + + if (!options->UseBridges && !server_mode(options)) { + if (tor_addr_family(addr) == AF_INET && + (!options->ClientUseIPv4 || (pref_only && pref_ipv6))) + return 0; + + if (tor_addr_family(addr) == AF_INET6 && + (!options->ClientUseIPv6 || (pref_only && !pref_ipv6))) + return 0; + } + return addr_policy_permits_tor_addr(addr, port, - reachable_or_addr_policy); + firewall_policy); } -/** Return true iff we think our firewall will let us make an OR connection to - * ri. */ +/** Return true iff we think our firewall will let us make a connection to + * addr:port. Uses ReachableORAddresses or ReachableDirAddresses based on + * fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. + */ int -fascist_firewall_allows_or(const routerinfo_t *ri) +fascist_firewall_allows_address_addr(const tor_addr_t *addr, uint16_t port, + firewall_connection_t fw_connection, + int pref_only) { - /* XXXX proposal 118 */ - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, ri->addr); - return fascist_firewall_allows_address_or(&addr, ri->or_port); -} + const or_options_t *options = get_options(); -/** Return true iff we think our firewall will let us make an OR connection to - * node. */ -int -fascist_firewall_allows_node(const node_t *node) -{ - if (node->ri) { - return fascist_firewall_allows_or(node->ri); - } else if (node->rs) { - tor_addr_t addr; - tor_addr_from_ipv4h(&addr, node->rs->addr); - return fascist_firewall_allows_address_or(&addr, node->rs->or_port); + if (fw_connection == FIREWALL_OR_CONNECTION) { + return fascist_firewall_allows_address(addr, port, + reachable_or_addr_policy, + pref_only, + nodelist_prefer_ipv6_orport(options)); + } else if (fw_connection == FIREWALL_DIR_CONNECTION) { + return fascist_firewall_allows_address(addr, port, + reachable_dir_addr_policy, + pref_only, + nodelist_prefer_ipv6_dirport(options)); } else { - return 1; + log_warn(LD_BUG, "Bad firewall_connection_t value %d.", + fw_connection); + return 0; } } -/** Return true iff we think our firewall will let us make a directory - * connection to addr:port. */ +/** Return true iff we think our firewall will let us make a connection to + * addr:port (ap). Uses ReachableORAddresses or ReachableDirAddresses based on + * fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. + */ int -fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port) +fascist_firewall_allows_address_ap(const tor_addr_port_t *ap, + firewall_connection_t fw_connection, + int pref_only) { - return addr_policy_permits_tor_addr(addr, port, - reachable_dir_addr_policy); + tor_assert(ap); + return fascist_firewall_allows_address_addr(&ap->addr, ap->port, + fw_connection, pref_only); +} + +/* Return true iff we think our firewall will let us make a connection to + * ipv4h_or_addr:ipv4_or_port. ipv4h_or_addr is interpreted in host order. + * Uses ReachableORAddresses or ReachableDirAddresses based on + * fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. */ +int +fascist_firewall_allows_address_ipv4h(uint32_t ipv4h_or_addr, + uint16_t ipv4_or_port, + firewall_connection_t fw_connection, + int pref_only) +{ + tor_addr_t ipv4_or_addr; + tor_addr_from_ipv4h(&ipv4_or_addr, ipv4h_or_addr); + return fascist_firewall_allows_address_addr(&ipv4_or_addr, ipv4_or_port, + fw_connection, pref_only); +} + +/** Return true iff we think our firewall will let us make a connection to + * ipv4h_addr/ipv6_addr. Uses ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. */ +static int +fascist_firewall_allows_base(uint32_t ipv4h_addr, uint16_t ipv4_orport, + uint16_t ipv4_dirport, + const tor_addr_t *ipv6_addr, uint16_t ipv6_orport, + uint16_t ipv6_dirport, + firewall_connection_t fw_connection, + int pref_only) +{ + if (fascist_firewall_allows_address_ipv4h(ipv4h_addr, + (fw_connection == FIREWALL_OR_CONNECTION + ? ipv4_orport + : ipv4_dirport), + fw_connection, + pref_only)) { + return 1; + } + + if (fascist_firewall_allows_address_addr(ipv6_addr, + (fw_connection == FIREWALL_OR_CONNECTION + ? ipv6_orport + : ipv6_dirport), + fw_connection, + pref_only)) { + return 1; + } + + return 0; +} + +/** Like fascist_firewall_allows_ri, but doesn't consult the node. */ +static int +fascist_firewall_allows_ri_impl(const routerinfo_t *ri, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!ri) { + return 0; + } + + /* Assume IPv4 and IPv6 DirPorts are the same */ + return fascist_firewall_allows_base(ri->addr, ri->or_port, ri->dir_port, + &ri->ipv6_addr, ri->ipv6_orport, + ri->dir_port, fw_connection, pref_only); +} + +/** Return true iff we think our firewall will let us make a connection to + * ri on either its IPv4 or IPv6 address. Uses + * or_port/ipv6_orport/ReachableORAddresses or dir_port/ReachableDirAddresses + * based on IPv4/IPv6 and fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. + * Consults the corresponding node if the addresses in ri are not permitted. */ +int +fascist_firewall_allows_ri(const routerinfo_t *ri, + firewall_connection_t fw_connection, int pref_only) +{ + if (!ri) { + return 0; + } + + /* Assume IPv4 and IPv6 DirPorts are the same */ + if (fascist_firewall_allows_ri_impl(ri, fw_connection, pref_only)) { + return 1; + } else { + const node_t *node = node_get_by_id(ri->cache_info.identity_digest); + return fascist_firewall_allows_node(node, fw_connection, pref_only); + } +} + +/** Like fascist_firewall_allows_rs, but doesn't consult the node. */ +static int +fascist_firewall_allows_rs_impl(const routerstatus_t *rs, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!rs) { + return 0; + } + + /* Assume IPv4 and IPv6 DirPorts are the same */ + return fascist_firewall_allows_base(rs->addr, rs->or_port, rs->dir_port, + &rs->ipv6_addr, rs->ipv6_orport, + rs->dir_port, fw_connection, pref_only); +} + +/** Return true iff we think our firewall will let us make a connection to + * rs on either its IPv4 or IPv6 address. Uses + * or_port/ipv6_orport/ReachableORAddresses or dir_port/ReachableDirAddresses + * based on IPv4/IPv6 and fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. + * Consults the corresponding node if the addresses in rs are not permitted. */ +int +fascist_firewall_allows_rs(const routerstatus_t *rs, + firewall_connection_t fw_connection, int pref_only) +{ + if (!rs) { + return 0; + } + + /* Assume IPv4 and IPv6 DirPorts are the same */ + if (fascist_firewall_allows_rs_impl(rs, fw_connection, pref_only)) { + return 1; + } else { + const node_t *node = node_get_by_id(rs->identity_digest); + return fascist_firewall_allows_node(node, fw_connection, pref_only); + } +} + +/** Like fascist_firewall_allows_md, but doesn't consult the node. */ +static int +fascist_firewall_allows_md_impl(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!md) { + return 0; + } + + /* Can't check dirport, it doesn't have one */ + if (fw_connection == FIREWALL_DIR_CONNECTION) { + return 0; + } + + /* Also can't check IPv4, doesn't have that either */ + return fascist_firewall_allows_address_addr(&md->ipv6_addr, md->ipv6_orport, + fw_connection, pref_only); +} + +/** Return true iff we think our firewall will let us make a connection to + * md on its IPv6 address. (The IPv4 address is in the routerstatus and + * the routerinfo.) Uses ipv6_orport/ReachableORAddresses or + * dir_port/ReachableDirAddresses based on fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. + * Consults the corresponding node if the address in md is not permitted. */ +int +fascist_firewall_allows_md(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!md) { + return 0; + } + + if (fascist_firewall_allows_md_impl(md, fw_connection, pref_only)) { + return 1; + } else { + networkstatus_t *ns; + ns = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + if (!ns) { + return 0; + } + const routerstatus_t *rs; + rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); + if (!rs) { + return 0; + } + const node_t *node = node_get_by_id(rs->identity_digest); + return fascist_firewall_allows_node(node, fw_connection, pref_only); + } +} + +/** Return true iff we think our firewall will let us make a connection to + * node: + * - if preferred is true, on its preferred address, + * - if not, on either its IPv4 or IPv6 address. + * Uses or_port/ipv6_orport/ReachableORAddresses or + * dir_port/ReachableDirAddresses based on IPv4/IPv6 and fw_connection. + * If pref_only, return false if addr is not in the client's preferred address + * family. */ +int +fascist_firewall_allows_node(const node_t *node, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!node) { + return 0; + } + + node_assert_ok(node); + + /* Sometimes, the rs is missing the IPv6 address info, and we need to go + * all the way to the md */ + if (node->ri && fascist_firewall_allows_ri_impl(node->ri, fw_connection, + pref_only)) { + return 1; + } else if (node->rs && fascist_firewall_allows_rs_impl(node->rs, + fw_connection, + pref_only)) { + return 1; + } else if (node->md && fascist_firewall_allows_md_impl(node->md, + fw_connection, + pref_only)) { + return 1; + } else { + /* If we know nothing, assume it's unreachable, we'll never get an address + * to connect to. */ + return 0; + } +} + +/** Return true iff we think our firewall will let us make a connection to + * ds on either its IPv4 or IPv6 address. Uses ReachableORAddresses or + * ReachableDirAddresses based on fw_connection (some directory + * connections are tunneled over ORPorts). + * If pref_only, return false if addr is not in the client's preferred address + * family. */ +int +fascist_firewall_allows_dir_server(const dir_server_t *ds, + firewall_connection_t fw_connection, + int pref_only) +{ + if (!ds) { + return 0; + } + + /* A dir_server_t always has a fake_status. As long as it has the same + * addresses/ports in both fake_status and dir_server_t, this works fine. + * (See #17867.) + * This function relies on fascist_firewall_allows_rs looking up the node on + * failure, because it will get the latest info for the relay. */ + return fascist_firewall_allows_rs(&ds->fake_status, fw_connection, + pref_only); +} + +/** If a and b are both valid and allowed by fw_connection, + * choose one based on want_a and return it. + * Otherwise, return whichever is allowed. + * Otherwise, return NULL. + * If pref_only, only return an address if it's in the client's preferred + * address family. */ +static const tor_addr_port_t * +fascist_firewall_choose_address_impl(const tor_addr_port_t *a, + const tor_addr_port_t *b, + int want_a, + firewall_connection_t fw_connection, + int pref_only) +{ + const tor_addr_port_t *use_a = NULL; + const tor_addr_port_t *use_b = NULL; + + if (fascist_firewall_allows_address_ap(a, fw_connection, pref_only)) { + use_a = a; + } + + if (fascist_firewall_allows_address_ap(b, fw_connection, pref_only)) { + use_b = b; + } + + /* If both are allowed */ + if (use_a && use_b) { + /* Choose a if we want it */ + return (want_a ? use_a : use_b); + } else { + /* Choose a if we have it */ + return (use_a ? use_a : use_b); + } +} + +/** If a and b are both valid and preferred by fw_connection, + * choose one based on want_a and return it. + * Otherwise, return whichever is preferred. + * If neither are preferred, and pref_only is false: + * - If a and b are both allowed by fw_connection, + * choose one based on want_a and return it. + * - Otherwise, return whichever is preferred. + * Otherwise, return NULL. */ +const tor_addr_port_t * +fascist_firewall_choose_address(const tor_addr_port_t *a, + const tor_addr_port_t *b, + int want_a, + firewall_connection_t fw_connection, + int pref_only) +{ + const tor_addr_port_t *pref = fascist_firewall_choose_address_impl( + a, b, want_a, + fw_connection, + 1); + if (pref_only || pref) { + /* If there is a preferred address, use it. If we can only use preferred + * addresses, and neither address is preferred, pref will be NULL, and we + * want to return NULL, so return it. */ + return pref; + } else { + /* If there's no preferred address, and we can return addresses that are + * not preferred, use an address that's allowed */ + return fascist_firewall_choose_address_impl(a, b, want_a, fw_connection, + 0); + } +} + +/** Copy an address and port into ap that we think our firewall will + * let us connect to. Uses ipv4_addr/ipv6_addr and + * ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, only choose preferred addresses. In either case, choose + * a preferred address before an address that's not preferred. + * If neither address is chosen, return 0, else return 1. */ +static int +fascist_firewall_choose_address_base(const tor_addr_t *ipv4_addr, + uint16_t ipv4_orport, + uint16_t ipv4_dirport, + const tor_addr_t *ipv6_addr, + uint16_t ipv6_orport, + uint16_t ipv6_dirport, + firewall_connection_t fw_connection, + int pref_only, + tor_addr_port_t* ap) +{ + const tor_addr_port_t *result = NULL; + /* This argument is ignored as long as the address pair is IPv4/IPv6, + * because we always have a preference in a client. + * For bridge clients, this selects the preferred address, which was + * previously IPv6 (if a bridge has both), so we keep that behaviour. */ + const int bridge_client_prefer_ipv4 = 0; + + tor_assert(ipv6_addr); + tor_assert(ap); + + tor_addr_port_t ipv4_ap; + tor_addr_copy(&ipv4_ap.addr, ipv4_addr); + ipv4_ap.port = (fw_connection == FIREWALL_OR_CONNECTION + ? ipv4_orport + : ipv4_dirport); + + tor_addr_port_t ipv6_ap; + tor_addr_copy(&ipv6_ap.addr, ipv6_addr); + ipv6_ap.port = (fw_connection == FIREWALL_OR_CONNECTION + ? ipv6_orport + : ipv6_dirport); + + result = fascist_firewall_choose_address(&ipv4_ap, &ipv6_ap, + bridge_client_prefer_ipv4, + fw_connection, pref_only); + + if (result) { + tor_addr_copy(&ap->addr, &result->addr); + ap->port = result->port; + return 1; + } else { + return 0; + } +} + +/** Like fascist_firewall_choose_address_base, but takes a host-order IPv4 + * address as the first parameter. */ +static int +fascist_firewall_choose_address_ipv4h(uint32_t ipv4h_addr, + uint16_t ipv4_orport, + uint16_t ipv4_dirport, + const tor_addr_t *ipv6_addr, + uint16_t ipv6_orport, + uint16_t ipv6_dirport, + firewall_connection_t fw_connection, + int pref_only, + tor_addr_port_t* ap) +{ + tor_addr_t ipv4_addr; + tor_addr_from_ipv4h(&ipv4_addr, ipv4h_addr); + return fascist_firewall_choose_address_base(&ipv4_addr, ipv4_orport, + ipv4_dirport, ipv6_addr, + ipv6_orport, ipv6_dirport, + fw_connection, pref_only, ap); +} + +#define IPV6_OR_LOOKUP(r, identity_digest, ipv6_or_ap) \ + STMT_BEGIN \ + if (!(r)->ipv6_orport || tor_addr_is_null(&(r)->ipv6_addr)) { \ + const node_t *node = node_get_by_id((identity_digest)); \ + if (node) { \ + node_get_pref_ipv6_orport(node, &(ipv6_or_ap)); \ + } else { \ + tor_addr_make_null(&(ipv6_or_ap).addr, AF_INET6); \ + (ipv6_or_ap).port = 0; \ + } \ + } else { \ + tor_addr_copy(&(ipv6_or_ap).addr, &(r)->ipv6_addr); \ + (ipv6_or_ap).port = (r)->ipv6_orport; \ + } \ + STMT_END + +/** Copy an address and port from ri into ap that we think our + * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and + * ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, only choose preferred addresses. In either case, choose + * a preferred address before an address that's not preferred. + * If neither address is chosen, return 0, else return 1. + * Consults the corresponding node if the addresses in ri are not valid. */ +int +fascist_firewall_choose_address_ri(const routerinfo_t *ri, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap) +{ + if (!ri) { + return 0; + } + + tor_assert(ap); + + /* Don't do the lookup if the IPv6 address/port in ri is OK. + * If it's OK, assume the dir_port is also OK. */ + tor_addr_port_t ipv6_or_ap; + IPV6_OR_LOOKUP(ri, ri->cache_info.identity_digest, ipv6_or_ap); + + /* Assume IPv4 and IPv6 DirPorts are the same. + * Assume the IPv6 OR and Dir addresses are the same. */ + return fascist_firewall_choose_address_ipv4h(ri->addr, + ri->or_port, + ri->dir_port, + &ipv6_or_ap.addr, + ipv6_or_ap.port, + ri->dir_port, + fw_connection, + pref_only, + ap); +} + +/** Copy an address and port from rs into ap that we think our + * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and + * ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, only choose preferred addresses. In either case, choose + * a preferred address before an address that's not preferred. + * If neither address is chosen, return 0, else return 1. + * Consults the corresponding node if the addresses in rs are not valid. */ +int +fascist_firewall_choose_address_rs(const routerstatus_t *rs, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap) +{ + if (!rs) { + return 0; + } + + tor_assert(ap); + + /* Don't do the lookup if the IPv6 address/port in rs is OK. + * If it's OK, assume the dir_port is also OK. */ + tor_addr_port_t ipv6_or_ap; + IPV6_OR_LOOKUP(rs, rs->identity_digest, ipv6_or_ap); + + /* Assume IPv4 and IPv6 DirPorts are the same. + * Assume the IPv6 OR and Dir addresses are the same. */ + return fascist_firewall_choose_address_ipv4h(rs->addr, + rs->or_port, + rs->dir_port, + &ipv6_or_ap.addr, + ipv6_or_ap.port, + rs->dir_port, + fw_connection, + pref_only, + ap); +} + +/* Copy the IPv6 address and ORPort from md into ap if we think + * our firewall will let us connect to it. Uses ReachableORAddresses. + * If pref_only, only copy if it's a preferred address. + * If fw_connection is FIREWALL_DIR_CONNECTION, don't copy the address. + * If the address isn't copied, return 0, else return 1. */ +static int +fascist_firewall_choose_address_md_impl(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap) +{ + if (!md) { + return 0; + } + + /* Can't check dirport, it doesn't have one */ + if (fw_connection == FIREWALL_DIR_CONNECTION) { + return 0; + } + + tor_assert(ap); + + if (fascist_firewall_allows_md(md, fw_connection, pref_only)) { + tor_addr_copy(&ap->addr, &md->ipv6_addr); + ap->port = md->ipv6_orport; + return 1; + } else { + return 0; + } +} + +/** Lookup the node for md, and call fascist_firewall_choose_address_node on + * it. If any step in this process fails, fall back to calling + * fascist_firewall_choose_address_md_impl. */ +int +fascist_firewall_choose_address_md(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap) +{ + if (!md) { + return 0; + } + + tor_assert(ap); + + /* If we can't get the node, */ + networkstatus_t *ns; + ns = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); + if (!ns) { + return fascist_firewall_choose_address_md_impl(md, fw_connection, + pref_only, ap); + } + const routerstatus_t *rs; + rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); + if (!rs) { + return fascist_firewall_choose_address_md_impl(md, fw_connection, + pref_only, ap); + } + const node_t *node = node_get_by_id(rs->identity_digest); + if (node) { + return fascist_firewall_choose_address_node(node, fw_connection, + pref_only, ap); + } else { + return fascist_firewall_choose_address_md_impl(md, fw_connection, + pref_only, ap); + } +} + +/** Copy an address and port from node into ap that we think our + * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and + * ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, only choose preferred addresses. In either case, choose + * a preferred address before an address that's not preferred. + * If neither address is chosen, return 0, else return 1. */ +int +fascist_firewall_choose_address_node(const node_t *node, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t *ap) +{ + if (!node) { + return 0; + } + + node_assert_ok(node); + + tor_addr_port_t ipv4_or_ap; + node_get_prim_orport(node, &ipv4_or_ap); + tor_addr_port_t ipv4_dir_ap; + node_get_prim_dirport(node, &ipv4_dir_ap); + + tor_addr_port_t ipv6_or_ap; + node_get_pref_ipv6_orport(node, &ipv6_or_ap); + tor_addr_port_t ipv6_dir_ap; + node_get_pref_ipv6_dirport(node, &ipv6_dir_ap); + + /* Assume the IPv6 OR and Dir addresses are the same. */ + return fascist_firewall_choose_address_base(&ipv4_or_ap.addr, + ipv4_or_ap.port, + ipv4_dir_ap.port, + &ipv6_or_ap.addr, + ipv6_or_ap.port, + ipv6_dir_ap.port, + fw_connection, + pref_only, + ap); +} + +/** Copy an address and port from ds into ap that we think our + * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and + * ipv4_orport/ipv6_orport/ReachableORAddresses or + * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and + * fw_connection. + * If pref_only, only choose preferred addresses. In either case, choose + * a preferred address before an address that's not preferred. + * If neither address is chosen, return 0, else return 1. */ +int +fascist_firewall_choose_address_dir_server(const dir_server_t *ds, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t *ap) +{ + if (!ds) { + return 0; + } + + /* A dir_server_t always has a fake_status. As long as it has the same + * addresses/ports in both fake_status and dir_server_t, this works fine. + * (See #17867.) + * This function relies on fascist_firewall_choose_address_rs looking up the + * addresses from the node if it can, because that will get the latest info + * for the relay. */ + return fascist_firewall_choose_address_rs(&ds->fake_status, fw_connection, + pref_only, ap); } /** Return 1 if addr is permitted to connect to our dir port, diff --git a/src/or/policies.h b/src/or/policies.h index 007f494482..7309bcf667 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -22,13 +22,61 @@ #define EXIT_POLICY_REJECT_PRIVATE (1 << 1) #define EXIT_POLICY_ADD_DEFAULT (1 << 2) +typedef enum firewall_connection_t { + FIREWALL_OR_CONNECTION = 0, + FIREWALL_DIR_CONNECTION = 1 +} firewall_connection_t; + typedef int exit_policy_parser_cfg_t; -int firewall_is_fascist_or(void); -int fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port); -int fascist_firewall_allows_or(const routerinfo_t *ri); -int fascist_firewall_allows_node(const node_t *node); -int fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port); +int fascist_firewall_allows_address_addr(const tor_addr_t *addr, uint16_t port, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_address_ap(const tor_addr_port_t *ap, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_address_ipv4h(uint32_t ipv4h_or_addr, + uint16_t ipv4_or_port, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_ri(const routerinfo_t *ri, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_rs(const routerstatus_t *rs, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_md(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_node(const node_t *node, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_allows_dir_server(const dir_server_t *ds, + firewall_connection_t fw_connection, + int pref_only); + +const tor_addr_port_t * fascist_firewall_choose_address( + const tor_addr_port_t *a, + const tor_addr_port_t *b, + int want_a, + firewall_connection_t fw_connection, + int pref_only); +int fascist_firewall_choose_address_ri(const routerinfo_t *ri, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap); +int fascist_firewall_choose_address_rs(const routerstatus_t *rs, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap); +int fascist_firewall_choose_address_md(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap); +int fascist_firewall_choose_address_node(const node_t *node, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap); +int fascist_firewall_choose_address_dir_server(const dir_server_t *ds, + firewall_connection_t fw_connection, + int pref_only, tor_addr_port_t* ap); + int dir_policy_permits_address(const tor_addr_t *addr); int socks_policy_permits_address(const tor_addr_t *addr); int authdir_policy_permits_address(uint32_t addr, uint16_t port); @@ -94,6 +142,10 @@ addr_policy_result_t compare_tor_addr_to_short_policy( #ifdef POLICIES_PRIVATE STATIC void append_exit_policy_string(smartlist_t **policy, const char *more); +STATIC int fascist_firewall_allows_address(const tor_addr_t *addr, + uint16_t port, + smartlist_t *firewall_policy, + int pref_only, int pref_ipv6); #endif #endif diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 0011d3698a..87276dbbf8 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -624,6 +624,100 @@ test_entry_is_live(void *arg) ; /* XXX */ } +static or_options_t mocked_options; + +static const or_options_t * +mock_get_options(void) +{ + return &mocked_options; +} + +#define TEST_IPV4_ADDR "123.45.67.89" +#define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]" + +static void +test_node_preferred_orport(void *arg) +{ + (void)arg; + tor_addr_t ipv4_addr; + const uint16_t ipv4_port = 4444; + tor_addr_t ipv6_addr; + const uint16_t ipv6_port = 6666; + routerinfo_t node_ri; + node_t node; + tor_addr_port_t ap; + + /* Setup options */ + memset(&mocked_options, 0, sizeof(mocked_options)); + /* We don't test ClientPreferIPv6ORPort here, because it's only used in + * nodelist_set_consensus to setup each node_t. */ + MOCK(get_options, mock_get_options); + + /* Setup IP addresses */ + tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR); + tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR); + + /* Setup node_ri */ + memset(&node_ri, 0, sizeof(node_ri)); + node_ri.addr = tor_addr_to_ipv4h(&ipv4_addr); + node_ri.or_port = ipv4_port; + tor_addr_copy(&node_ri.ipv6_addr, &ipv6_addr); + node_ri.ipv6_orport = ipv6_port; + + /* Setup node */ + memset(&node, 0, sizeof(node)); + node.ri = &node_ri; + + /* Check the preferred address is IPv4 if we're only using IPv4, regardless + * of whether we prefer it or not */ + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientUseIPv6 = 0; + node.ipv6_preferred = 0; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr)); + tt_assert(ap.port == ipv4_port); + + node.ipv6_preferred = 1; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr)); + tt_assert(ap.port == ipv4_port); + + /* Check the preferred address is IPv4 if we're using IPv4 and IPv6, but + * don't prefer the IPv6 address */ + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientUseIPv6 = 1; + node.ipv6_preferred = 0; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr)); + tt_assert(ap.port == ipv4_port); + + /* Check the preferred address is IPv6 if we prefer it and + * ClientUseIPv6 is 1, regardless of ClientUseIPv4 */ + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientUseIPv6 = 1; + node.ipv6_preferred = 1; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr)); + tt_assert(ap.port == ipv6_port); + + mocked_options.ClientUseIPv4 = 0; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr)); + tt_assert(ap.port == ipv6_port); + + /* Check the preferred address is IPv6 if we don't prefer it, but + * ClientUseIPv4 is 0 */ + mocked_options.ClientUseIPv4 = 0; + mocked_options.ClientUseIPv6 = 1; + node.ipv6_preferred = 0; + node_get_pref_orport(&node, &ap); + tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr)); + tt_assert(ap.port == ipv6_port); + + done: + UNMOCK(get_options); +} + static const struct testcase_setup_t fake_network = { fake_network_setup, fake_network_cleanup }; @@ -654,6 +748,9 @@ struct testcase_t entrynodes_tests[] = { { "entry_is_live", test_entry_is_live, TT_FORK, &fake_network, NULL }, + { "node_preferred_orport", + test_node_preferred_orport, + 0, NULL, NULL }, END_OF_TESTCASES }; diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 4f5565e575..077d1b2af5 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1127,6 +1127,449 @@ test_policies_getinfo_helper_policies(void *arg) #undef TEST_IPV4_ADDR #undef TEST_IPV6_ADDR +#define TEST_IPV4_ADDR_STR "1.2.3.4" +#define TEST_IPV6_ADDR_STR "[1002::4567]" +#define REJECT_IPv4_FINAL_STR "reject 0.0.0.0/0:*" +#define REJECT_IPv6_FINAL_STR "reject [::]/0:*" + +#define OTHER_IPV4_ADDR_STR "6.7.8.9" +#define OTHER_IPV6_ADDR_STR "[afff::]" + +/** Run unit tests for fascist_firewall_allows_address */ +static void +test_policies_fascist_firewall_allows_address(void *arg) +{ + (void)arg; + tor_addr_t ipv4_addr, ipv6_addr, r_ipv4_addr, r_ipv6_addr; + tor_addr_t n_ipv4_addr, n_ipv6_addr; + const uint16_t port = 1234; + smartlist_t *policy = NULL; + smartlist_t *e_policy = NULL; + addr_policy_t *item = NULL; + int malformed_list = 0; + + /* Setup the options and the items in the policies */ + memset(&mock_options, 0, sizeof(or_options_t)); + MOCK(get_options, mock_get_options); + + policy = smartlist_new(); + item = router_parse_addr_policy_item_from_string("accept " + TEST_IPV4_ADDR_STR ":*", + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(item); + tt_assert(!malformed_list); + smartlist_add(policy, item); + item = router_parse_addr_policy_item_from_string("accept " + TEST_IPV6_ADDR_STR, + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(item); + tt_assert(!malformed_list); + smartlist_add(policy, item); + /* Normally, policy_expand_unspec would do this for us */ + item = router_parse_addr_policy_item_from_string(REJECT_IPv4_FINAL_STR, + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(item); + tt_assert(!malformed_list); + smartlist_add(policy, item); + item = router_parse_addr_policy_item_from_string(REJECT_IPv6_FINAL_STR, + ADDR_POLICY_ACCEPT, + &malformed_list); + tt_assert(item); + tt_assert(!malformed_list); + smartlist_add(policy, item); + item = NULL; + + e_policy = smartlist_new(); + + /* + char *polstr = policy_dump_to_string(policy, 1, 1); + printf("%s\n", polstr); + tor_free(polstr); + */ + + /* Parse the addresses */ + tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR_STR); + tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR_STR); + tor_addr_parse(&r_ipv4_addr, OTHER_IPV4_ADDR_STR); + tor_addr_parse(&r_ipv6_addr, OTHER_IPV6_ADDR_STR); + tor_addr_make_null(&n_ipv4_addr, AF_INET); + tor_addr_make_null(&n_ipv6_addr, AF_INET6); + + /* Test the function's address matching with IPv4 and IPv6 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Preferring IPv4 */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0) + == 0); + + /* Preferring IPv6 */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1) + == 0); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1) + == 0); + + /* Test the function's address matching with UseBridges on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 0; + mock_options.ClientUseIPv6 = 0; + mock_options.UseBridges = 1; + + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Test the function's address matching with IPv4 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 0; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Test the function's address matching with IPv6 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 0; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Test the function's address matching with everything off */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 0; + mock_options.ClientUseIPv6 = 0; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Test the function's address matching for unusual inputs */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 1; + + /* NULL and tor_addr_is_null addresses are rejected */ + tt_assert(fascist_firewall_allows_address(NULL, port, policy, 0, 0) == 0); + tt_assert(fascist_firewall_allows_address(&n_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&n_ipv6_addr, port, policy, 0, 0) + == 0); + + /* zero ports are rejected */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, 0, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, 0, policy, 0, 0) + == 0); + + /* NULL and empty policies accept everything */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, NULL, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, NULL, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, e_policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, e_policy, 0, 0) + == 1); + + done: + addr_policy_free(item); + addr_policy_list_free(policy); + addr_policy_list_free(e_policy); + UNMOCK(get_options); +} + +#undef REJECT_IPv4_FINAL_STR +#undef REJECT_IPv6_FINAL_STR +#undef OTHER_IPV4_ADDR_STR +#undef OTHER_IPV6_ADDR_STR + +#define TEST_IPV4_OR_PORT 1234 +#define TEST_IPV4_DIR_PORT 2345 +#define TEST_IPV6_OR_PORT 61234 +#define TEST_IPV6_DIR_PORT 62345 + +/** Run unit tests for fascist_firewall_choose_address */ +static void +test_policies_fascist_firewall_choose_address(void *arg) +{ + (void)arg; + tor_addr_port_t ipv4_or_ap, ipv4_dir_ap, ipv6_or_ap, ipv6_dir_ap; + tor_addr_port_t n_ipv4_ap, n_ipv6_ap; + + /* Setup the options */ + memset(&mock_options, 0, sizeof(or_options_t)); + MOCK(get_options, mock_get_options); + + /* Parse the addresses */ + tor_addr_parse(&ipv4_or_ap.addr, TEST_IPV4_ADDR_STR); + ipv4_or_ap.port = TEST_IPV4_OR_PORT; + tor_addr_parse(&ipv4_dir_ap.addr, TEST_IPV4_ADDR_STR); + ipv4_dir_ap.port = TEST_IPV4_DIR_PORT; + + tor_addr_parse(&ipv6_or_ap.addr, TEST_IPV6_ADDR_STR); + ipv6_or_ap.port = TEST_IPV6_OR_PORT; + tor_addr_parse(&ipv6_dir_ap.addr, TEST_IPV6_ADDR_STR); + ipv6_dir_ap.port = TEST_IPV6_DIR_PORT; + + tor_addr_make_null(&n_ipv4_ap.addr, AF_INET); + n_ipv4_ap.port = 0; + tor_addr_make_null(&n_ipv6_ap.addr, AF_INET6); + n_ipv6_ap.port = 0; + + /* Choose an address with IPv4 and IPv6 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 0; + + /* Preferring IPv4 */ + mock_options.ClientPreferIPv6ORPort = 0; + mock_options.ClientPreferIPv6DirPort = 0; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv4_dir_ap); + + /* Preferring IPv6 */ + mock_options.ClientPreferIPv6ORPort = 1; + mock_options.ClientPreferIPv6DirPort = 1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); + + /* Preferring IPv4 OR / IPv6 Dir */ + mock_options.ClientPreferIPv6ORPort = 0; + mock_options.ClientPreferIPv6DirPort = 1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); + + /* Preferring IPv6 OR / IPv4 Dir */ + mock_options.ClientPreferIPv6ORPort = 1; + mock_options.ClientPreferIPv6DirPort = 0; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv4_dir_ap); + + /* Choose an address with UseBridges on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.UseBridges = 1; + + for (mock_options.ClientUseIPv4 = 0; mock_options.ClientUseIPv4 <= 1; + mock_options.ClientUseIPv4++) { + for (mock_options.ClientUseIPv6 = 0; mock_options.ClientUseIPv6 <= 1; + mock_options.ClientUseIPv6++) { + for (mock_options.ClientPreferIPv6ORPort = 0; + mock_options.ClientPreferIPv6ORPort <= 1; + mock_options.ClientPreferIPv6ORPort++) { + for (mock_options.ClientPreferIPv6DirPort = 0; + mock_options.ClientPreferIPv6DirPort <= 1; + mock_options.ClientPreferIPv6DirPort++) { + /* This (ab)uses the actual enum values */ + tt_assert(FIREWALL_OR_CONNECTION < FIREWALL_DIR_CONNECTION); + for (firewall_connection_t fw_connection = FIREWALL_OR_CONNECTION; + fw_connection <= FIREWALL_DIR_CONNECTION; fw_connection++) { + for (int pref_only = 0; pref_only <= 1; pref_only++) { + + /* Ignoring all other settings, want_a should choose the address + * for bridge clients */ + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, + &ipv6_or_ap, 1, + fw_connection, + pref_only) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, + &ipv6_or_ap, 0, + fw_connection, + pref_only) + == &ipv6_or_ap); + } + } + } + } + } + } + + /* Choose an address with IPv4 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 0; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv4_dir_ap); + + /* Choose an address with IPv6 on */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 0; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); + + /* Choose an address with everything off */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 0; + mock_options.ClientUseIPv6 = 0; + mock_options.UseBridges = 0; + + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == NULL); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == NULL); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == NULL); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == NULL); + + /* Choose from unusual inputs */ + memset(&mock_options, 0, sizeof(or_options_t)); + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; + mock_options.UseBridges = 1; + + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &n_ipv6_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == NULL); + + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &n_ipv6_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == NULL); + + done: + UNMOCK(get_options); +} + +#undef TEST_IPV4_ADDR_STR +#undef TEST_IPV6_ADDR_STR +#undef TEST_IPV4_OR_PORT +#undef TEST_IPV4_DIR_PORT +#undef TEST_IPV6_OR_PORT +#undef TEST_IPV6_DIR_PORT + struct testcase_t policy_tests[] = { { "router_dump_exit_policy_to_string", test_dump_exit_policy_to_string, 0, NULL, NULL }, @@ -1137,6 +1580,10 @@ struct testcase_t policy_tests[] = { { "reject_interface_address", test_policies_reject_interface_address, 0, NULL, NULL }, { "reject_port_address", test_policies_reject_port_address, 0, NULL, NULL }, + { "fascist_firewall_allows_address", + test_policies_fascist_firewall_allows_address, 0, NULL, NULL }, + { "fascist_firewall_choose_address", + test_policies_fascist_firewall_choose_address, 0, NULL, NULL }, END_OF_TESTCASES }; From 268608c0a0605e596d1a884ee35d432c88bac38b Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 18 Dec 2015 11:28:54 +1100 Subject: [PATCH 03/15] Choose OR Entry Guards using IPv4/IPv6 preferences Update unit tests. --- src/or/circuitbuild.c | 15 +++--- src/or/or.h | 4 +- src/or/routerlist.c | 18 ++++++-- src/or/routerlist.h | 3 +- src/test/test_entrynodes.c | 93 ++++++++++++++++++++++++++++++++++---- 5 files changed, 108 insertions(+), 25 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index d44fcd74ef..dcb9de3a80 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -1778,7 +1778,7 @@ pick_tor2web_rendezvous_node(router_crn_flags_t flags, router_add_running_nodes_to_smartlist(all_live_nodes, allow_invalid, 0, 0, 0, - need_desc); + need_desc, 0); /* Filter all_live_nodes to only add live *and* whitelisted RPs to * the list whitelisted_live_rps. */ @@ -2144,7 +2144,9 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) const node_t *choice; smartlist_t *excluded; const or_options_t *options = get_options(); - router_crn_flags_t flags = CRN_NEED_GUARD|CRN_NEED_DESC; + /* If possible, choose an entry server with a preferred address, + * otherwise, choose one with an allowed address */ + router_crn_flags_t flags = CRN_NEED_GUARD|CRN_NEED_DESC|CRN_PREF_ADDR; const node_t *node; if (state && options->UseEntryGuards && @@ -2161,12 +2163,6 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state) * family. */ nodelist_add_node_and_family(excluded, node); } - /* Exclude all ORs that we can't reach through our firewall */ - smartlist_t *nodes = nodelist_get_list(); - SMARTLIST_FOREACH(nodes, const node_t *, node, { - if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) - smartlist_add(excluded, (void*)node); - }); /* and exclude current entry guards and their families, * unless we're in a test network, and excluding guards * would exclude all nodes (i.e. we're in an incredibly small tor network, @@ -2332,8 +2328,9 @@ extend_info_from_node(const node_t *node, int for_direct_connect) if (node->ri == NULL && (node->rs == NULL || node->md == NULL)) return NULL; + /* Choose a preferred address first, but fall back to an allowed address*/ if (for_direct_connect) - node_get_pref_orport(node, &ap); + fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, &ap); else node_get_prim_orport(node, &ap); diff --git a/src/or/or.h b/src/or/or.h index b1765d1d57..412789cafe 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -5221,7 +5221,9 @@ typedef enum { CRN_ALLOW_INVALID = 1<<3, /* XXXX not used, apparently. */ CRN_WEIGHT_AS_EXIT = 1<<5, - CRN_NEED_DESC = 1<<6 + CRN_NEED_DESC = 1<<6, + /* On clients, only provide nodes that satisfy ClientPreferIPv6OR */ + CRN_PREF_ADDR = 1<<7 } router_crn_flags_t; /** Return value for router_add_to_routerlist() and dirserv_add_descriptor() */ diff --git a/src/or/routerlist.c b/src/or/routerlist.c index c45854c52f..804ff29edb 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1826,7 +1826,8 @@ routerlist_add_node_and_family(smartlist_t *sl, const routerinfo_t *router) void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, int need_uptime, int need_capacity, - int need_guard, int need_desc) + int need_guard, int need_desc, + int pref_addr) { /* XXXX MOVE */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { if (!node->is_running || @@ -1838,6 +1839,9 @@ router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, continue; if (node_is_unreliable(node, need_uptime, need_capacity, need_guard)) continue; + /* Choose a node with a preferred OR address */ + if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, pref_addr)) + continue; smartlist_add(sl, (void *)node); } SMARTLIST_FOREACH_END(node); @@ -2299,6 +2303,10 @@ node_sl_choose_by_bandwidth(const smartlist_t *sl, * If CRN_NEED_DESC is set in flags, we only consider nodes that * have a routerinfo or microdescriptor -- that is, enough info to be * used to build a circuit. + * If CRN_PREF_ADDR is set in flags, we only consider nodes that + * have an address that is preferred by the ClientPreferIPv6ORPort setting + * (regardless of this flag, we exclude nodes that aren't allowed by the + * firewall, including ClientUseIPv4 0 and ClientUseIPv6 0). */ const node_t * router_choose_random_node(smartlist_t *excludedsmartlist, @@ -2311,6 +2319,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, const int allow_invalid = (flags & CRN_ALLOW_INVALID) != 0; const int weight_for_exit = (flags & CRN_WEIGHT_AS_EXIT) != 0; const int need_desc = (flags & CRN_NEED_DESC) != 0; + const int pref_addr = (flags & CRN_PREF_ADDR) != 0; smartlist_t *sl=smartlist_new(), *excludednodes=smartlist_new(); @@ -2336,7 +2345,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, router_add_running_nodes_to_smartlist(sl, allow_invalid, need_uptime, need_capacity, - need_guard, need_desc); + need_guard, need_desc, pref_addr); log_debug(LD_CIRC, "We found %d running nodes.", smartlist_len(sl)); @@ -2365,7 +2374,7 @@ router_choose_random_node(smartlist_t *excludedsmartlist, choice = node_sl_choose_by_bandwidth(sl, rule); smartlist_free(sl); - if (!choice && (need_uptime || need_capacity || need_guard)) { + if (!choice && (need_uptime || need_capacity || need_guard || pref_addr)) { /* try once more -- recurse but with fewer restrictions. */ log_info(LD_CIRC, "We couldn't find any live%s%s%s routers; falling back " @@ -2373,7 +2382,8 @@ router_choose_random_node(smartlist_t *excludedsmartlist, need_capacity?", fast":"", need_uptime?", stable":"", need_guard?", guard":""); - flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD); + flags &= ~ (CRN_NEED_UPTIME|CRN_NEED_CAPACITY|CRN_NEED_GUARD| + CRN_PREF_ADDR); choice = router_choose_random_node( excludedsmartlist, excludedset, flags); } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 339e34ae03..4c828d6572 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -61,7 +61,8 @@ void router_reset_status_download_failures(void); int routers_have_same_or_addrs(const routerinfo_t *r1, const routerinfo_t *r2); void router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, int need_uptime, int need_capacity, - int need_guard, int need_desc); + int need_guard, int need_desc, + int pref_addr); const routerinfo_t *routerlist_find_my_routerinfo(void); uint32_t router_get_advertised_bandwidth(const routerinfo_t *router); diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 87276dbbf8..e4947e0959 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -70,6 +70,14 @@ fake_network_setup(const struct testcase_t *testcase) return dummy_state; } +static or_options_t mocked_options; + +static const or_options_t * +mock_get_options(void) +{ + return &mocked_options; +} + /** Test choose_random_entry() with none of our routers being guard nodes. */ static void test_choose_random_entry_no_guards(void *arg) @@ -78,6 +86,14 @@ test_choose_random_entry_no_guards(void *arg) (void) arg; + MOCK(get_options, mock_get_options); + + /* Check that we get a guard if it passes preferred + * address settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientPreferIPv6ORPort = 0; + /* Try to pick an entry even though none of our routers are guards. */ chosen_entry = choose_random_entry(NULL); @@ -86,8 +102,35 @@ test_choose_random_entry_no_guards(void *arg) can't find a proper entry guard. */ tt_assert(chosen_entry); + /* And with the other IP version active */ + mocked_options.ClientUseIPv6 = 1; + chosen_entry = choose_random_entry(NULL); + tt_assert(chosen_entry); + + /* Check that we don't get a guard if it doesn't pass mandatory address + * settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 0; + mocked_options.ClientPreferIPv6ORPort = 0; + + chosen_entry = choose_random_entry(NULL); + + /* If we don't allow IPv4 at all, we don't get a guard*/ + tt_assert(!chosen_entry); + + /* Check that we get a guard if it passes allowed but not preferred address + * settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientUseIPv6 = 1; + mocked_options.ClientPreferIPv6ORPort = 1; + + chosen_entry = choose_random_entry(NULL); + tt_assert(chosen_entry); + done: - ; + memset(&mocked_options, 0, sizeof(mocked_options)); + UNMOCK(get_options); } /** Test choose_random_entry() with only one of our routers being a @@ -101,17 +144,55 @@ test_choose_random_entry_one_possible_guard(void *arg) (void) arg; + MOCK(get_options, mock_get_options); + /* Set one of the nodes to be a guard. */ our_nodelist = nodelist_get_list(); the_guard = smartlist_get(our_nodelist, 4); /* chosen by fair dice roll */ the_guard->is_possible_guard = 1; + /* Check that we get the guard if it passes preferred + * address settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientPreferIPv6ORPort = 0; + /* Pick an entry. Make sure we pick the node we marked as guard. */ chosen_entry = choose_random_entry(NULL); tt_ptr_op(chosen_entry, OP_EQ, the_guard); + /* And with the other IP version active */ + mocked_options.ClientUseIPv6 = 1; + chosen_entry = choose_random_entry(NULL); + tt_ptr_op(chosen_entry, OP_EQ, the_guard); + + /* Check that we don't get a guard if it doesn't pass mandatory address + * settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 0; + mocked_options.ClientPreferIPv6ORPort = 0; + + chosen_entry = choose_random_entry(NULL); + + /* If we don't allow IPv4 at all, we don't get a guard*/ + tt_assert(!chosen_entry); + + /* Check that we get a node if it passes allowed but not preferred + * address settings */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientUseIPv6 = 1; + mocked_options.ClientPreferIPv6ORPort = 1; + + chosen_entry = choose_random_entry(NULL); + + /* We disable the guard check and the preferred address check at the same + * time, so we can't be sure we get the guard */ + tt_assert(chosen_entry); + done: - ; + memset(&mocked_options, 0, sizeof(mocked_options)); + UNMOCK(get_options); } /** Helper to conduct tests for populate_live_entry_guards(). @@ -624,14 +705,6 @@ test_entry_is_live(void *arg) ; /* XXX */ } -static or_options_t mocked_options; - -static const or_options_t * -mock_get_options(void) -{ - return &mocked_options; -} - #define TEST_IPV4_ADDR "123.45.67.89" #define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]" From e72cbf7a4e346f0d379961520db8bea7e9249f88 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 18 Dec 2015 11:29:47 +1100 Subject: [PATCH 04/15] Choose directory servers by IPv4/IPv6 preferences Add unit tests, refactor pick_directory functions. --- src/or/connection.c | 6 +- src/or/connection.h | 6 +- src/or/directory.c | 180 ++++++++++++++++++++++------- src/or/routerlist.c | 224 ++++++++++++++++++++++++++----------- src/or/routerlist.h | 2 + src/test/test_routerlist.c | 73 ++++++++++++ 6 files changed, 375 insertions(+), 116 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index 9765a8ea91..d59e07bdbe 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -4252,10 +4252,10 @@ connection_write_to_buf_impl_,(const char *string, size_t len, /** Return a connection with given type, address, port, and purpose; * or NULL if no such connection exists (or if all such connections are marked * for close). */ -connection_t * -connection_get_by_type_addr_port_purpose(int type, +MOCK_IMPL(connection_t *, +connection_get_by_type_addr_port_purpose,(int type, const tor_addr_t *addr, uint16_t port, - int purpose) + int purpose)) { CONN_GET_TEMPLATE(conn, (conn->type == type && diff --git a/src/or/connection.h b/src/or/connection.h index 59ea6d898e..ec5c3945ec 100644 --- a/src/or/connection.h +++ b/src/or/connection.h @@ -186,9 +186,9 @@ connection_get_outbuf_len(connection_t *conn) connection_t *connection_get_by_global_id(uint64_t id); connection_t *connection_get_by_type(int type); -connection_t *connection_get_by_type_addr_port_purpose(int type, - const tor_addr_t *addr, - uint16_t port, int purpose); +MOCK_DECL(connection_t *,connection_get_by_type_addr_port_purpose,(int type, + const tor_addr_t *addr, + uint16_t port, int purpose)); connection_t *connection_get_by_type_state(int type, int state); connection_t *connection_get_by_type_state_rendquery(int type, int state, const char *rendquery); diff --git a/src/or/directory.c b/src/or/directory.c index d5531d88da..438b5d823f 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -82,18 +82,18 @@ static void dir_microdesc_download_failed(smartlist_t *failed, static void note_client_request(int purpose, int compressed, size_t bytes); static int client_likes_consensus(networkstatus_t *v, const char *want_url); -static void directory_initiate_command_rend(const tor_addr_t *addr, - uint16_t or_port, - uint16_t dir_port, - const char *digest, - uint8_t dir_purpose, - uint8_t router_purpose, - dir_indirection_t indirection, - const char *resource, - const char *payload, - size_t payload_len, - time_t if_modified_since, - const rend_data_t *rend_query); +static void directory_initiate_command_rend( + const tor_addr_port_t *or_addr_port, + const tor_addr_port_t *dir_addr_port, + const char *digest, + uint8_t dir_purpose, + uint8_t router_purpose, + dir_indirection_t indirection, + const char *resource, + const char *payload, + size_t payload_len, + time_t if_modified_since, + const rend_data_t *rend_query); /********* START VARIABLES **********/ @@ -624,8 +624,10 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, { const or_options_t *options = get_options(); const node_t *node; - tor_addr_t addr; + tor_addr_port_t use_or_ap, use_dir_ap; const int anonymized_connection = dirind_is_anon(indirection); + int have_or = 0, have_dir = 0; + node = node_get_by_id(status->identity_digest); if (!node && anonymized_connection) { @@ -634,7 +636,6 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, routerstatus_describe(status)); return; } - tor_addr_from_ipv4h(&addr, status->addr); if (options->ExcludeNodes && options->StrictNodes && routerset_contains_routerstatus(options->ExcludeNodes, status, -1)) { @@ -646,13 +647,68 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, return; } - directory_initiate_command_rend(&addr, - status->or_port, status->dir_port, - status->identity_digest, - dir_purpose, router_purpose, - indirection, resource, - payload, payload_len, if_modified_since, - rend_query); + /* At this point, if we are a clients making a direct connection to a + * directory server, we have selected a server that has at least one address + * allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This + * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if + * possible. (If UseBridges is set, clients ignore all these settings.) + * + * Now we use a similar process to select an address for the relay, + * but simply use the other address if the one we want isn't allowed by + * the firewall. + * + * (When Tor uploads and downloads a hidden service descriptor, it uses + * DIRIND_ANONYMOUS, except for Tor2Web, which uses DIRIND_ONEHOP. + * So this code will only modify the address for Tor2Web's HS descriptor + * fetches. Even Single Onion Servers (NYI) use DIRIND_ANONYMOUS, to avoid + * HSDirs denying service by rejecting descriptors.) + */ + + /* Initialise the OR / Dir addresses */ + tor_addr_make_null(&use_or_ap.addr, AF_UNSPEC); + use_or_ap.port = 0; + tor_addr_make_null(&use_dir_ap.addr, AF_UNSPEC); + use_dir_ap.port = 0; + + if (anonymized_connection) { + /* Use the primary (IPv4) OR address if we're making an indirect + * connection. */ + tor_addr_from_ipv4h(&use_or_ap.addr, status->addr); + use_or_ap.port = status->or_port; + have_or = 1; + } else { + /* We use an IPv6 address if we have one and we prefer it. + * Use the preferred address and port if they are reachable, otherwise, + * use the alternate address and port (if any). + */ + have_or = fascist_firewall_choose_address_rs(status, + FIREWALL_OR_CONNECTION, 0, + &use_or_ap); + } + + have_dir = fascist_firewall_choose_address_rs(status, + FIREWALL_DIR_CONNECTION, 0, + &use_dir_ap); + + /* We rejected both addresses. This isn't great. */ + if (!have_or && !have_dir) { + log_info(LD_DIR, "Rejected both the OR and Dir address when launching a " + "directory connection to: IPv4 %s OR %d Dir %d IPv6 %s OR %d " + "Dir %d", fmt_addr32(status->addr), status->or_port, + status->dir_port, fmt_addr(&status->ipv6_addr), + status->ipv6_orport, status->dir_port); + return; + } + + /* XX/teor - we don't retry the alternate OR/Dir address if this one fails. + * (See #6772.) Instead, we'll retry another directory on failure. */ + + directory_initiate_command_rend(&use_or_ap, &use_dir_ap, + status->identity_digest, + dir_purpose, router_purpose, + indirection, resource, + payload, payload_len, if_modified_since, + rend_query); } /** Launch a new connection to the directory server status to @@ -874,17 +930,21 @@ directory_command_should_use_begindir(const or_options_t *options, if (indirection == DIRIND_DIRECT_CONN || indirection == DIRIND_ANON_DIRPORT) return 0; if (indirection == DIRIND_ONEHOP) - if (!fascist_firewall_allows_address_or(addr, or_port) || + if (!fascist_firewall_allows_address_addr(addr, or_port, + FIREWALL_OR_CONNECTION, 0) || directory_fetches_from_authorities(options)) return 0; /* We're firewalled or are acting like a relay -- also no. */ return 1; } -/** Helper for directory_initiate_command_routerstatus: send the - * command to a server whose address is address, whose IP is - * addr, whose directory port is dir_port, whose tor version - * supports_begindir, and whose identity key digest is - * digest. */ +/** Helper for directory_initiate_command_rend: send the + * command to a server whose address is _addr, whose OR port is + * or_port, whose directory port is dir_port, whose identity key + * digest is digest, with purposes dir_purpose and + * router_purpose, making an (in)direct connection as specified in + * indirection, with command resource, payload of + * payload_len, and asking for a result only if_modified_since. + */ void directory_initiate_command(const tor_addr_t *_addr, uint16_t or_port, uint16_t dir_port, @@ -894,7 +954,27 @@ directory_initiate_command(const tor_addr_t *_addr, const char *payload, size_t payload_len, time_t if_modified_since) { - directory_initiate_command_rend(_addr, or_port, dir_port, + /* Assume _addr applies to both the ORPort and the DirPort, but use the + * null tor_addr if ORPort or DirPort are 0. */ + tor_addr_port_t or_ap, dir_ap; + + if (or_port) { + tor_addr_copy(&or_ap.addr, _addr); + } else { + /* the family doesn't matter here, so make it the same as _addr */ + tor_addr_make_null(&or_ap.addr, tor_addr_family(_addr)); + } + or_ap.port = or_port; + + if (dir_port) { + tor_addr_copy(&dir_ap.addr, _addr); + } else { + /* the family doesn't matter here, so make it the same as _addr */ + tor_addr_make_null(&dir_ap.addr, tor_addr_family(_addr)); + } + dir_ap.port = dir_port; + + directory_initiate_command_rend(&or_ap, &dir_ap, digest, dir_purpose, router_purpose, indirection, resource, payload, payload_len, @@ -914,10 +994,11 @@ is_sensitive_dir_purpose(uint8_t dir_purpose) } /** Same as directory_initiate_command(), but accepts rendezvous data to - * fetch a hidden service descriptor. */ + * fetch a hidden service descriptor, and takes its address & port arguments + * as tor_addr_port_t. */ static void -directory_initiate_command_rend(const tor_addr_t *_addr, - uint16_t or_port, uint16_t dir_port, +directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, + const tor_addr_port_t *dir_addr_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, dir_indirection_t indirection, @@ -926,19 +1007,24 @@ directory_initiate_command_rend(const tor_addr_t *_addr, time_t if_modified_since, const rend_data_t *rend_query) { + tor_assert(or_addr_port); + tor_assert(dir_addr_port); + tor_assert(or_addr_port->port || dir_addr_port->port); + tor_assert(digest); + dir_connection_t *conn; const or_options_t *options = get_options(); int socket_error = 0; - int use_begindir = directory_command_should_use_begindir(options, _addr, - or_port, router_purpose, indirection); + const int use_begindir = directory_command_should_use_begindir(options, + &or_addr_port->addr, or_addr_port->port, + router_purpose, indirection); const int anonymized_connection = dirind_is_anon(indirection); + const int or_connection = use_begindir || anonymized_connection; + tor_addr_t addr; - - tor_assert(_addr); - tor_assert(or_port || dir_port); - tor_assert(digest); - - tor_addr_copy(&addr, _addr); + tor_addr_copy(&addr, &(or_connection ? or_addr_port : dir_addr_port)->addr); + uint16_t port = (or_connection ? or_addr_port : dir_addr_port)->port; + uint16_t dir_port = dir_addr_port->port; log_debug(LD_DIR, "anonymized %d, use_begindir %d.", anonymized_connection, use_begindir); @@ -954,13 +1040,23 @@ directory_initiate_command_rend(const tor_addr_t *_addr, /* ensure that we don't make direct connections when a SOCKS server is * configured. */ - if (!anonymized_connection && !use_begindir && !options->HTTPProxy && + if (!or_connection && !options->HTTPProxy && (options->Socks4Proxy || options->Socks5Proxy)) { log_warn(LD_DIR, "Cannot connect to a directory server through a " "SOCKS proxy!"); return; } + if (or_connection && (!or_addr_port->port + || tor_addr_is_null(&or_addr_port->addr))) { + log_warn(LD_DIR, "Cannot make an OR connection without an OR port."); + return; + } else if (!or_connection && (!dir_addr_port->port + || tor_addr_is_null(&dir_addr_port->addr))) { + log_warn(LD_DIR, "Cannot make a Dir connection without a Dir port."); + return; + } + /* ensure we don't make excess connections when we're already downloading * a consensus during bootstrap */ if (connection_dir_avoid_extra_connection_for_purpose(dir_purpose)) { @@ -971,7 +1067,7 @@ directory_initiate_command_rend(const tor_addr_t *_addr, /* set up conn so it's got all the data we need to remember */ tor_addr_copy(&conn->base_.addr, &addr); - conn->base_.port = use_begindir ? or_port : dir_port; + conn->base_.port = port; conn->base_.address = tor_dup_addr(&addr); memcpy(conn->identity_digest, digest, DIGEST_LEN); @@ -989,7 +1085,7 @@ directory_initiate_command_rend(const tor_addr_t *_addr, if (rend_query) conn->rend_data = rend_data_dup(rend_query); - if (!anonymized_connection && !use_begindir) { + if (!or_connection) { /* then we want to connect to dirport directly */ if (options->HTTPProxy) { diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 804ff29edb..4a3c1cd434 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1462,9 +1462,122 @@ router_pick_dirserver_generic(smartlist_t *sourcelist, return router_pick_trusteddirserver_impl(sourcelist, type, flags, NULL); } +/* Check if we already have a directory fetch from ap, for serverdesc + * (including extrainfo) or microdesc documents. + * If so, return 1, if not, return 0. + * Also returns 0 if addr is NULL, tor_addr_is_null(addr), or dir_port is 0. + */ +STATIC int +router_is_already_dir_fetching(const tor_addr_port_t *ap, int serverdesc, + int microdesc) +{ + if (!ap || tor_addr_is_null(&ap->addr) || !ap->port) { + return 0; + } + + /* XX/teor - we're not checking tunnel connections here, see #17848 + */ + if (serverdesc && ( + connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &ap->addr, ap->port, DIR_PURPOSE_FETCH_SERVERDESC) + || connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &ap->addr, ap->port, DIR_PURPOSE_FETCH_EXTRAINFO))) { + return 1; + } + + if (microdesc && ( + connection_get_by_type_addr_port_purpose( + CONN_TYPE_DIR, &ap->addr, ap->port, DIR_PURPOSE_FETCH_MICRODESC))) { + return 1; + } + + return 0; +} + +/* Check if we already have a directory fetch from ds, for serverdesc + * (including extrainfo) or microdesc documents. + * If so, return 1, if not, return 0. + */ +static int +router_is_already_dir_fetching_ds(const dir_server_t *ds, + int serverdesc, + int microdesc) +{ + tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap; + + /* Assume IPv6 DirPort is the same as IPv4 DirPort */ + tor_addr_from_ipv4h(&ipv4_dir_ap.addr, ds->addr); + ipv4_dir_ap.port = ds->dir_port; + tor_addr_copy(&ipv6_dir_ap.addr, &ds->ipv6_addr); + ipv6_dir_ap.port = ds->dir_port; + + return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc) + || router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc)); +} + +/* Check if we already have a directory fetch from rs, for serverdesc + * (including extrainfo) or microdesc documents. + * If so, return 1, if not, return 0. + */ +static int +router_is_already_dir_fetching_rs(const routerstatus_t *rs, + int serverdesc, + int microdesc) +{ + tor_addr_port_t ipv4_dir_ap, ipv6_dir_ap; + + /* Assume IPv6 DirPort is the same as IPv4 DirPort */ + tor_addr_from_ipv4h(&ipv4_dir_ap.addr, rs->addr); + ipv4_dir_ap.port = rs->dir_port; + tor_addr_copy(&ipv6_dir_ap.addr, &rs->ipv6_addr); + ipv6_dir_ap.port = rs->dir_port; + + return (router_is_already_dir_fetching(&ipv4_dir_ap, serverdesc, microdesc) + || router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc)); +} + /** How long do we avoid using a directory server after it's given us a 503? */ #define DIR_503_TIMEOUT (60*60) +/* Common retry code for router_pick_directory_server_impl and + * router_pick_trusteddirserver_impl. Retry with the non-preferred IP version. + * Must be called before RETRY_WITHOUT_EXCLUDE(). + * + * If we got no result, and we are applying IP preferences, and we are a + * client that could use an alternate IP version, try again with the + * opposite preferences. */ +#define RETRY_ALTERNATE_IP_VERSION(retry_label) \ + STMT_BEGIN \ + if (result == NULL && try_ip_pref && options->ClientUseIPv4 \ + && options->ClientUseIPv6 && !server_mode(options) && n_not_preferred \ + && !n_busy) { \ + n_excluded = 0; \ + n_busy = 0; \ + try_ip_pref = 0; \ + n_not_preferred = 0; \ + goto retry_label; \ + } \ + STMT_END \ + +/* Common retry code for router_pick_directory_server_impl and + * router_pick_trusteddirserver_impl. Retry without excluding nodes, but with + * the preferred IP version. Must be called after RETRY_ALTERNATE_IP_VERSION(). + * + * If we got no result, and we are excluding nodes, and StrictNodes is + * not set, try again without excluding nodes. */ +#define RETRY_WITHOUT_EXCLUDE(retry_label) \ + STMT_BEGIN \ + if (result == NULL && try_excluding && !options->StrictNodes \ + && n_excluded && !n_busy) { \ + try_excluding = 0; \ + n_excluded = 0; \ + n_busy = 0; \ + try_ip_pref = 1; \ + n_not_preferred = 0; \ + goto retry_label; \ + } \ + STMT_END + /** Pick a random running valid directory server/mirror from our * routerlist. Arguments are as for router_pick_directory_server(), except: * @@ -1489,11 +1602,12 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, const int no_microdesc_fetching = (flags & PDS_NO_EXISTING_MICRODESC_FETCH); const int for_guard = (flags & PDS_FOR_GUARD); int try_excluding = 1, n_excluded = 0, n_busy = 0; + int try_ip_pref = 1, n_not_preferred = 0; if (!consensus) return NULL; - retry_without_exclude: + retry_search: direct = smartlist_new(); tunnel = smartlist_new(); @@ -1506,7 +1620,6 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { int is_trusted, is_trusted_extrainfo; int is_overloaded; - tor_addr_t addr; const routerstatus_t *status = node->rs; const country_t country = node->country; if (!status) @@ -1537,36 +1650,32 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, continue; } - /* XXXX IP6 proposal 118 */ - tor_addr_from_ipv4h(&addr, status->addr); - - if (no_serverdesc_fetching && ( - connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_SERVERDESC) - || connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_EXTRAINFO) - )) { - ++n_busy; - continue; - } - - if (no_microdesc_fetching && connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, status->dir_port, DIR_PURPOSE_FETCH_MICRODESC) - ) { + if (router_is_already_dir_fetching_rs(status, + no_serverdesc_fetching, + no_microdesc_fetching)) { ++n_busy; continue; } is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; - if ((!fascistfirewall || - fascist_firewall_allows_address_or(&addr, status->or_port))) + /* We use an IPv6 address if we have one and we prefer it. + * Add the router if its preferred address and port are reachable. + * If we don't get any routers, we'll try again with the non-preferred + * address for each router (if any). (To ensure correct load-balancing + * we try routers that only have one address both times.) + */ + if (!fascistfirewall || + fascist_firewall_allows_rs(status, FIREWALL_OR_CONNECTION, + try_ip_pref)) smartlist_add(is_trusted ? trusted_tunnel : is_overloaded ? overloaded_tunnel : tunnel, (void*)node); - else if (!fascistfirewall || - fascist_firewall_allows_address_dir(&addr, status->dir_port)) + else if (fascist_firewall_allows_rs(status, FIREWALL_DIR_CONNECTION, + try_ip_pref)) smartlist_add(is_trusted ? trusted_direct : is_overloaded ? overloaded_direct : direct, (void*)node); + else if (!tor_addr_is_null(&status->ipv6_addr)) + ++n_not_preferred; } SMARTLIST_FOREACH_END(node); if (smartlist_len(tunnel)) { @@ -1595,15 +1704,9 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); - if (result == NULL && try_excluding && !options->StrictNodes && n_excluded - && !n_busy) { - /* If we got no result, and we are excluding nodes, and StrictNodes is - * not set, try again without excluding nodes. */ - try_excluding = 0; - n_excluded = 0; - n_busy = 0; - goto retry_without_exclude; - } + RETRY_ALTERNATE_IP_VERSION(retry_search); + + RETRY_WITHOUT_EXCLUDE(retry_search); if (n_busy_out) *n_busy_out = n_busy; @@ -1658,11 +1761,12 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, smartlist_t *pick_from; int n_busy = 0; int try_excluding = 1, n_excluded = 0; + int try_ip_pref = 1, n_not_preferred = 0; if (!sourcelist) return NULL; - retry_without_exclude: + retry_search: direct = smartlist_new(); tunnel = smartlist_new(); @@ -1673,7 +1777,6 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, { int is_overloaded = d->fake_status.last_dir_503_at + DIR_503_TIMEOUT > now; - tor_addr_t addr; if (!d->is_running) continue; if ((type & d->type) == 0) continue; @@ -1689,35 +1792,27 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, continue; } - /* XXXX IP6 proposal 118 */ - tor_addr_from_ipv4h(&addr, d->addr); - - if (no_serverdesc_fetching) { - if (connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, d->dir_port, DIR_PURPOSE_FETCH_SERVERDESC) - || connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, d->dir_port, DIR_PURPOSE_FETCH_EXTRAINFO)) { - //log_debug(LD_DIR, "We have an existing connection to fetch " - // "descriptor from %s; delaying",d->description); - ++n_busy; - continue; - } - } - if (no_microdesc_fetching) { - if (connection_get_by_type_addr_port_purpose( - CONN_TYPE_DIR, &addr, d->dir_port, DIR_PURPOSE_FETCH_MICRODESC)) { - ++n_busy; - continue; - } + if (router_is_already_dir_fetching_ds(d, no_serverdesc_fetching, + no_microdesc_fetching)) { + ++n_busy; + continue; } - if (d->or_port && - (!fascistfirewall || - fascist_firewall_allows_address_or(&addr, d->or_port))) + /* We use an IPv6 address if we have one and we prefer it. + * Add the router if its preferred address and port are reachable. + * If we don't get any routers, we'll try again with the non-preferred + * address for each router (if any). (To ensure correct load-balancing + * we try routers that only have one address both times.) + */ + if (!fascistfirewall || + fascist_firewall_allows_dir_server(d, FIREWALL_OR_CONNECTION, + try_ip_pref)) smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, (void*)d); - else if (!fascistfirewall || - fascist_firewall_allows_address_dir(&addr, d->dir_port)) + else if (fascist_firewall_allows_dir_server(d, FIREWALL_DIR_CONNECTION, + try_ip_pref)) smartlist_add(is_overloaded ? overloaded_direct : direct, (void*)d); + else if (!tor_addr_is_null(&d->ipv6_addr)) + ++n_not_preferred; } SMARTLIST_FOREACH_END(d); @@ -1744,19 +1839,12 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, smartlist_free(overloaded_direct); smartlist_free(overloaded_tunnel); - if (result == NULL && try_excluding && !options->StrictNodes && n_excluded - && !n_busy) { - /* If we got no result, and we are excluding nodes, and StrictNodes is - * not set, try again without excluding nodes. */ - try_excluding = 0; - n_excluded = 0; - n_busy = 0; - goto retry_without_exclude; - } + RETRY_ALTERNATE_IP_VERSION(retry_search); + + RETRY_WITHOUT_EXCLUDE(retry_search); if (n_busy_out) *n_busy_out = n_busy; - return result; } diff --git a/src/or/routerlist.h b/src/or/routerlist.h index 4c828d6572..ac286d904f 100644 --- a/src/or/routerlist.h +++ b/src/or/routerlist.h @@ -243,6 +243,8 @@ MOCK_DECL(STATIC was_router_added_t, extrainfo_insert, MOCK_DECL(STATIC void, initiate_descriptor_downloads, (const routerstatus_t *source, int purpose, smartlist_t *digests, int lo, int hi, int pds_flags)); +STATIC int router_is_already_dir_fetching(const tor_addr_port_t *ap, + int serverdesc, int microdesc); #endif diff --git a/src/test/test_routerlist.c b/src/test/test_routerlist.c index 1bc5e4bb16..7ec6525ffb 100644 --- a/src/test/test_routerlist.c +++ b/src/test/test_routerlist.c @@ -5,6 +5,7 @@ #include "or.h" #include "routerlist.h" #include "directory.h" +#include "connection.h" #include "test.h" /* 4 digests + 3 sep + pre + post + NULL */ @@ -94,12 +95,84 @@ test_routerlist_launch_descriptor_downloads(void *arg) smartlist_free(downloadable); } +connection_t *mocked_connection = NULL; + +/* Mock connection_get_by_type_addr_port_purpose by returning + * mocked_connection. */ +static connection_t * +mock_connection_get_by_type_addr_port_purpose(int type, + const tor_addr_t *addr, + uint16_t port, int purpose) +{ + (void)type; + (void)addr; + (void)port; + (void)purpose; + + return mocked_connection; +} + +#define TEST_ADDR_STR "127.0.0.1" +#define TEST_DIR_PORT 12345 + +static void +test_routerlist_router_is_already_dir_fetching(void *arg) +{ + (void)arg; + tor_addr_port_t test_ap, null_addr_ap, zero_port_ap; + + /* Setup */ + tor_addr_parse(&test_ap.addr, TEST_ADDR_STR); + test_ap.port = TEST_DIR_PORT; + tor_addr_make_null(&null_addr_ap.addr, AF_INET6); + null_addr_ap.port = TEST_DIR_PORT; + tor_addr_parse(&zero_port_ap.addr, TEST_ADDR_STR); + zero_port_ap.port = 0; + MOCK(connection_get_by_type_addr_port_purpose, + mock_connection_get_by_type_addr_port_purpose); + + /* Test that we never get 1 from a NULL connection */ + mocked_connection = NULL; + tt_assert(router_is_already_dir_fetching(&test_ap, 1, 1) == 0); + tt_assert(router_is_already_dir_fetching(&test_ap, 1, 0) == 0); + tt_assert(router_is_already_dir_fetching(&test_ap, 0, 1) == 0); + /* We always expect 0 in these cases */ + tt_assert(router_is_already_dir_fetching(&test_ap, 0, 0) == 0); + tt_assert(router_is_already_dir_fetching(NULL, 1, 1) == 0); + tt_assert(router_is_already_dir_fetching(&null_addr_ap, 1, 1) == 0); + tt_assert(router_is_already_dir_fetching(&zero_port_ap, 1, 1) == 0); + + /* Test that we get 1 with a connection in the appropriate circumstances */ + mocked_connection = connection_new(CONN_TYPE_DIR, AF_INET); + tt_assert(router_is_already_dir_fetching(&test_ap, 1, 1) == 1); + tt_assert(router_is_already_dir_fetching(&test_ap, 1, 0) == 1); + tt_assert(router_is_already_dir_fetching(&test_ap, 0, 1) == 1); + + /* Test that we get 0 even with a connection in the appropriate + * circumstances */ + tt_assert(router_is_already_dir_fetching(&test_ap, 0, 0) == 0); + tt_assert(router_is_already_dir_fetching(NULL, 1, 1) == 0); + tt_assert(router_is_already_dir_fetching(&null_addr_ap, 1, 1) == 0); + tt_assert(router_is_already_dir_fetching(&zero_port_ap, 1, 1) == 0); + + done: + /* If a connection is never set up, connection_free chokes on it. */ + buf_free(mocked_connection->inbuf); + buf_free(mocked_connection->outbuf); + tor_free(mocked_connection); + UNMOCK(connection_get_by_type_addr_port_purpose); +} + +#undef TEST_ADDR_STR +#undef TEST_DIR_PORT + #define NODE(name, flags) \ { #name, test_routerlist_##name, (flags), NULL, NULL } struct testcase_t routerlist_tests[] = { NODE(initiate_descriptor_downloads, 0), NODE(launch_descriptor_downloads, 0), + NODE(router_is_already_dir_fetching, TT_FORK), END_OF_TESTCASES }; From c3cc8e16e9655ffcaead811675c360b6764f2992 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Tue, 22 Dec 2015 11:31:54 +1100 Subject: [PATCH 05/15] Log when IPv4/IPv6 restrictions or preferences weren't met --- src/or/connection.c | 65 +++++++++++++++++++++++++++++++++++++++++++++ src/or/directory.c | 12 ++++++--- src/or/routerlist.c | 49 ++++++++++++++++++++++++++++++++++ 3 files changed, 123 insertions(+), 3 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index d59e07bdbe..f252d2faa1 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -19,6 +19,7 @@ */ #define TOR_CHANNEL_INTERNAL_ #define CONNECTION_PRIVATE +#include "backtrace.h" #include "channel.h" #include "channeltls.h" #include "circuitbuild.h" @@ -1721,6 +1722,66 @@ connection_connect_sockaddr,(connection_t *conn, return inprogress ? 0 : 1; } +/* Log a message if connection violates ClientUseIPv4 0 or ClientUseIPv6 0. + * Log a less severe message if we couldn't conform to ClientPreferIPv6ORPort + * or ClientPreferIPv6ORPort. */ +static void +connection_connect_log_client_use_ip_version(const connection_t *conn) +{ + const or_options_t *options = get_options(); + + /* Only non-bridge clients care about ClientUseIPv4/6, bail out early on + * servers and bridge clients */ + if (options->UseBridges || server_mode(options) || !conn + || conn->type == CONN_TYPE_EXIT) { + return; + } + + /* We're only prepared to log OR and DIR connections here */ + if (conn->type != CONN_TYPE_OR && conn->type != CONN_TYPE_DIR) { + return; + } + + const int must_ipv4 = (options->ClientUseIPv6 == 0); + const int must_ipv6 = (options->ClientUseIPv4 == 0); + const int pref_ipv6 = (conn->type == CONN_TYPE_OR + ? nodelist_prefer_ipv6_orport(options) + : nodelist_prefer_ipv6_dirport(options)); + tor_addr_t real_addr; + tor_addr_make_null(&real_addr, AF_UNSPEC); + + /* OR conns keep the original address in real_addr, as addr gets overwritten + * with the descriptor address */ + if (conn->type == CONN_TYPE_OR) { + const or_connection_t *or_conn = TO_OR_CONN((connection_t *)conn); + tor_addr_copy(&real_addr, &or_conn->real_addr); + } else if (conn->type == CONN_TYPE_DIR) { + tor_addr_copy(&real_addr, &conn->addr); + } + + /* Check if we broke a mandatory address family restriction */ + if ((must_ipv4 && tor_addr_family(&real_addr) == AF_INET6) + || (must_ipv6 && tor_addr_family(&real_addr) == AF_INET)) { + log_warn(LD_BUG, "%s connection to %s violated ClientUseIPv%s 0.", + conn->type == CONN_TYPE_OR ? "OR" : "Dir", + fmt_addr(&real_addr), + options->ClientUseIPv4 == 0 ? "4" : "6"); + log_backtrace(LOG_WARN, LD_BUG, "Address came from"); + } + + /* Check if we couldn't satisfy an address family preference */ + if ((!pref_ipv6 && tor_addr_family(&real_addr) == AF_INET6) + || (pref_ipv6 && tor_addr_family(&real_addr) == AF_INET)) { + log_info(LD_NET, "Connection to %s doesn't satisfy ClientPreferIPv6%sPort " + "%d, with ClientUseIPv4 %d and ClientUseIPv6 %d.", + fmt_addr(&real_addr), + conn->type == CONN_TYPE_OR ? "OR" : "Dir", + conn->type == CONN_TYPE_OR ? options->ClientPreferIPv6ORPort + : options->ClientPreferIPv6DirPort, + options->ClientUseIPv4, options->ClientUseIPv4); + } +} + /** Take conn, make a nonblocking socket; try to connect to * addr:port (port arrives in *host order*). If fail, return -1 and if * applicable put your best guess about errno into *socket_error. @@ -1745,6 +1806,10 @@ connection_connect(connection_t *conn, const char *address, const or_options_t *options = get_options(); int protocol_family; + /* Log if we didn't stick to ClientUseIPv4/6 or ClientPreferIPv6OR/DirPort + */ + connection_connect_log_client_use_ip_version(conn); + if (tor_addr_family(addr) == AF_INET6) protocol_family = PF_INET6; else diff --git a/src/or/directory.c b/src/or/directory.c index 438b5d823f..20ffcee8dd 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -4,6 +4,7 @@ /* See LICENSE for licensing information */ #include "or.h" +#include "backtrace.h" #include "buffers.h" #include "circuitbuild.h" #include "config.h" @@ -692,11 +693,13 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, /* We rejected both addresses. This isn't great. */ if (!have_or && !have_dir) { - log_info(LD_DIR, "Rejected both the OR and Dir address when launching a " - "directory connection to: IPv4 %s OR %d Dir %d IPv6 %s OR %d " - "Dir %d", fmt_addr32(status->addr), status->or_port, + log_warn(LD_BUG, "Rejected all OR and Dir addresses from %s when " + "launching a directory connection to: IPv4 %s OR %d Dir %d " + "IPv6 %s OR %d Dir %d", routerstatus_describe(status), + fmt_addr32(status->addr), status->or_port, status->dir_port, fmt_addr(&status->ipv6_addr), status->ipv6_orport, status->dir_port); + log_backtrace(LOG_WARN, LD_BUG, "Addresses came from"); return; } @@ -1050,10 +1053,13 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, if (or_connection && (!or_addr_port->port || tor_addr_is_null(&or_addr_port->addr))) { log_warn(LD_DIR, "Cannot make an OR connection without an OR port."); + log_backtrace(LOG_WARN, LD_BUG, "Address came from"); return; } else if (!or_connection && (!dir_addr_port->port || tor_addr_is_null(&dir_addr_port->addr))) { log_warn(LD_DIR, "Cannot make a Dir connection without a Dir port."); + log_backtrace(LOG_WARN, LD_BUG, "Address came from"); + return; } diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 4a3c1cd434..2923da311f 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -13,6 +13,7 @@ #define ROUTERLIST_PRIVATE #include "or.h" +#include "backtrace.h" #include "crypto_ed25519.h" #include "circuitstats.h" #include "config.h" @@ -1536,6 +1537,50 @@ router_is_already_dir_fetching_rs(const routerstatus_t *rs, || router_is_already_dir_fetching(&ipv6_dir_ap, serverdesc, microdesc)); } +#ifndef LOG_FALSE_POSITIVES_DURING_BOOTSTRAP +#define LOG_FALSE_POSITIVES_DURING_BOOTSTRAP 0 +#endif + +/* Log a message if rs is not found or not a preferred address */ +static void +router_picked_poor_directory_log(const routerstatus_t *rs) +{ + const networkstatus_t *usable_consensus; + usable_consensus = networkstatus_get_reasonably_live_consensus(time(NULL), + usable_consensus_flavor()); + +#if !LOG_FALSE_POSITIVES_DURING_BOOTSTRAP + /* Don't log early in the bootstrap process, it's normal to pick from a + * small pool of nodes. Of course, this won't help if we're trying to + * diagnose bootstrap issues. */ + if (!smartlist_len(nodelist_get_list()) || !usable_consensus + || !router_have_minimum_dir_info()) { + return; + } +#endif + + /* We couldn't find a node, or the one we have doesn't fit our preferences. + * This might be a bug. */ + if (!rs) { + log_warn(LD_BUG, "Firewall denied all OR and Dir addresses for all relays " + "when searching for a directory."); + log_backtrace(LOG_WARN, LD_BUG, "Node search initiated by"); + } else if (!fascist_firewall_allows_rs(rs, FIREWALL_OR_CONNECTION, 1) + && !fascist_firewall_allows_rs(rs, FIREWALL_DIR_CONNECTION, 1) + ) { + log_warn(LD_BUG, "Selected a directory %s with non-preferred OR and Dir " + "addresses for launching a connection: " + "IPv4 %s OR %d Dir %d IPv6 %s OR %d Dir %d", + routerstatus_describe(rs), + fmt_addr32(rs->addr), rs->or_port, + rs->dir_port, fmt_addr(&rs->ipv6_addr), + rs->ipv6_orport, rs->dir_port); + log_backtrace(LOG_WARN, LD_BUG, "Node search initiated by"); + } +} + +#undef LOG_FALSE_POSITIVES_DURING_BOOTSTRAP + /** How long do we avoid using a directory server after it's given us a 503? */ #define DIR_503_TIMEOUT (60*60) @@ -1711,6 +1756,8 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, if (n_busy_out) *n_busy_out = n_busy; + router_picked_poor_directory_log(result ? result->rs : NULL); + return result ? result->rs : NULL; } @@ -1843,6 +1890,8 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, RETRY_WITHOUT_EXCLUDE(retry_search); + router_picked_poor_directory_log(result); + if (n_busy_out) *n_busy_out = n_busy; return result; From 16486662038de53c482cd6f50a30505f2bf20453 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Sun, 3 Jan 2016 18:20:37 +1100 Subject: [PATCH 06/15] Choose bridge addresses by IPv4/IPv6 preferences --- src/or/directory.c | 176 +++++++++++++++++++++++++++----------------- src/or/directory.h | 4 +- src/or/entrynodes.c | 13 +++- src/or/router.c | 5 +- 4 files changed, 123 insertions(+), 75 deletions(-) diff --git a/src/or/directory.c b/src/or/directory.c index 20ffcee8dd..665ba27767 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -497,11 +497,14 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( const node_t *node = choose_random_dirguard(type); if (node && node->ri) { /* every bridge has a routerinfo. */ - tor_addr_t addr; routerinfo_t *ri = node->ri; - node_get_addr(node, &addr); - directory_initiate_command(&addr, - ri->or_port, 0/*no dirport*/, + /* clients always make OR connections to bridges */ + tor_addr_port_t or_ap; + /* we are willing to use a non-preferred address if we need to */ + fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, + &or_ap); + directory_initiate_command(&or_ap.addr, or_ap.port, + NULL, 0, /*no dirport*/ ri->cache_info.identity_digest, dir_purpose, router_purpose, @@ -610,6 +613,80 @@ dirind_is_anon(dir_indirection_t ind) return ind == DIRIND_ANON_DIRPORT || ind == DIRIND_ANONYMOUS; } +/* Choose reachable OR and Dir addresses and ports from status, copying them + * into use_or_ap and use_dir_ap. If indirection is anonymous, then we're + * connecting via another relay, so choose the primary IPv4 address and ports. + * + * status should have at least one reachable address, if we can't choose a + * reachable address, warn and return -1. Otherwise, return 0. + */ +static int +directory_choose_address_routerstatus(const routerstatus_t *status, + dir_indirection_t indirection, + tor_addr_port_t *use_or_ap, + tor_addr_port_t *use_dir_ap) +{ + tor_assert(status != NULL); + tor_assert(use_or_ap != NULL); + tor_assert(use_dir_ap != NULL); + + const int anonymized_connection = dirind_is_anon(indirection); + int have_or = 0, have_dir = 0; + + /* We expect status to have at least one reachable address if we're + * connecting to it directly. + * + * Therefore, we can simply use the other address if the one we want isn't + * allowed by the firewall. + * + * (When Tor uploads and downloads a hidden service descriptor, it uses + * DIRIND_ANONYMOUS, except for Tor2Web, which uses DIRIND_ONEHOP. + * So this code will only modify the address for Tor2Web's HS descriptor + * fetches. Even Single Onion Servers (NYI) use DIRIND_ANONYMOUS, to avoid + * HSDirs denying service by rejecting descriptors.) + */ + + /* Initialise the OR / Dir addresses */ + tor_addr_make_null(&use_or_ap->addr, AF_UNSPEC); + use_or_ap->port = 0; + tor_addr_make_null(&use_dir_ap->addr, AF_UNSPEC); + use_dir_ap->port = 0; + + if (anonymized_connection) { + /* Use the primary (IPv4) OR address if we're making an indirect + * connection. */ + tor_addr_from_ipv4h(&use_or_ap->addr, status->addr); + use_or_ap->port = status->or_port; + have_or = 1; + } else { + /* We use an IPv6 address if we have one and we prefer it. + * Use the preferred address and port if they are reachable, otherwise, + * use the alternate address and port (if any). + */ + have_or = fascist_firewall_choose_address_rs(status, + FIREWALL_OR_CONNECTION, 0, + use_or_ap); + } + + have_dir = fascist_firewall_choose_address_rs(status, + FIREWALL_DIR_CONNECTION, 0, + use_dir_ap); + + /* We rejected both addresses. This isn't great. */ + if (!have_or && !have_dir) { + log_warn(LD_BUG, "Rejected all OR and Dir addresses from %s when " + "launching a directory connection to: IPv4 %s OR %d Dir %d " + "IPv6 %s OR %d Dir %d", routerstatus_describe(status), + fmt_addr32(status->addr), status->or_port, + status->dir_port, fmt_addr(&status->ipv6_addr), + status->ipv6_orport, status->dir_port); + log_backtrace(LOG_WARN, LD_BUG, "Addresses came from"); + return -1; + } + + return 0; +} + /** Same as directory_initiate_command_routerstatus(), but accepts * rendezvous data to fetch a hidden service descriptor. */ void @@ -627,7 +704,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, const node_t *node; tor_addr_port_t use_or_ap, use_dir_ap; const int anonymized_connection = dirind_is_anon(indirection); - int have_or = 0, have_dir = 0; + + tor_assert(status != NULL); node = node_get_by_id(status->identity_digest); @@ -654,57 +732,16 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if * possible. (If UseBridges is set, clients ignore all these settings.) * - * Now we use a similar process to select an address for the relay, - * but simply use the other address if the one we want isn't allowed by - * the firewall. - * - * (When Tor uploads and downloads a hidden service descriptor, it uses - * DIRIND_ANONYMOUS, except for Tor2Web, which uses DIRIND_ONEHOP. - * So this code will only modify the address for Tor2Web's HS descriptor - * fetches. Even Single Onion Servers (NYI) use DIRIND_ANONYMOUS, to avoid - * HSDirs denying service by rejecting descriptors.) + * Now choose an address that we can use to connect to the directory server. */ - - /* Initialise the OR / Dir addresses */ - tor_addr_make_null(&use_or_ap.addr, AF_UNSPEC); - use_or_ap.port = 0; - tor_addr_make_null(&use_dir_ap.addr, AF_UNSPEC); - use_dir_ap.port = 0; - - if (anonymized_connection) { - /* Use the primary (IPv4) OR address if we're making an indirect - * connection. */ - tor_addr_from_ipv4h(&use_or_ap.addr, status->addr); - use_or_ap.port = status->or_port; - have_or = 1; - } else { - /* We use an IPv6 address if we have one and we prefer it. - * Use the preferred address and port if they are reachable, otherwise, - * use the alternate address and port (if any). - */ - have_or = fascist_firewall_choose_address_rs(status, - FIREWALL_OR_CONNECTION, 0, - &use_or_ap); - } - - have_dir = fascist_firewall_choose_address_rs(status, - FIREWALL_DIR_CONNECTION, 0, - &use_dir_ap); - - /* We rejected both addresses. This isn't great. */ - if (!have_or && !have_dir) { - log_warn(LD_BUG, "Rejected all OR and Dir addresses from %s when " - "launching a directory connection to: IPv4 %s OR %d Dir %d " - "IPv6 %s OR %d Dir %d", routerstatus_describe(status), - fmt_addr32(status->addr), status->or_port, - status->dir_port, fmt_addr(&status->ipv6_addr), - status->ipv6_orport, status->dir_port); - log_backtrace(LOG_WARN, LD_BUG, "Addresses came from"); + if (directory_choose_address_routerstatus(status, indirection, &use_or_ap, + &use_dir_ap) < 0) { return; } - /* XX/teor - we don't retry the alternate OR/Dir address if this one fails. - * (See #6772.) Instead, we'll retry another directory on failure. */ + /* We don't retry the alternate OR/Dir address for the same directory if + * the address we choose fails (#6772). + * Instead, we'll retry another directory on failure. */ directory_initiate_command_rend(&use_or_ap, &use_dir_ap, status->identity_digest, @@ -941,41 +978,42 @@ directory_command_should_use_begindir(const or_options_t *options, } /** Helper for directory_initiate_command_rend: send the - * command to a server whose address is _addr, whose OR port is - * or_port, whose directory port is dir_port, whose identity key - * digest is digest, with purposes dir_purpose and + * command to a server whose OR address/port is or_addr/or_port, + * whose directory address/port is dir_addr/dir_port, whose + * identity key digest is digest, with purposes dir_purpose and * router_purpose, making an (in)direct connection as specified in * indirection, with command resource, payload of * payload_len, and asking for a result only if_modified_since. */ void -directory_initiate_command(const tor_addr_t *_addr, - uint16_t or_port, uint16_t dir_port, +directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, + const tor_addr_t *dir_addr, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since) { - /* Assume _addr applies to both the ORPort and the DirPort, but use the - * null tor_addr if ORPort or DirPort are 0. */ tor_addr_port_t or_ap, dir_ap; - if (or_port) { - tor_addr_copy(&or_ap.addr, _addr); + /* Use the null tor_addr and 0 port if the address or port isn't valid. */ + if (tor_addr_port_is_valid(or_addr, or_port, 0)) { + tor_addr_copy(&or_ap.addr, or_addr); + or_ap.port = or_port; } else { - /* the family doesn't matter here, so make it the same as _addr */ - tor_addr_make_null(&or_ap.addr, tor_addr_family(_addr)); + /* the family doesn't matter here, so make it IPv4 */ + tor_addr_make_null(&or_ap.addr, AF_INET); + or_port = 0; } - or_ap.port = or_port; - if (dir_port) { - tor_addr_copy(&dir_ap.addr, _addr); + if (tor_addr_port_is_valid(dir_addr, dir_port, 0)) { + tor_addr_copy(&dir_ap.addr, dir_addr); + dir_ap.port = dir_port; } else { - /* the family doesn't matter here, so make it the same as _addr */ - tor_addr_make_null(&dir_ap.addr, tor_addr_family(_addr)); + /* the family doesn't matter here, so make it IPv4 */ + tor_addr_make_null(&dir_ap.addr, AF_INET); + dir_port = 0; } - dir_ap.port = dir_port; directory_initiate_command_rend(&or_ap, &dir_ap, digest, dir_purpose, diff --git a/src/or/directory.h b/src/or/directory.h index 2644e5703e..61c29baf06 100644 --- a/src/or/directory.h +++ b/src/or/directory.h @@ -66,8 +66,8 @@ int connection_dir_process_inbuf(dir_connection_t *conn); int connection_dir_finished_flushing(dir_connection_t *conn); int connection_dir_finished_connecting(dir_connection_t *conn); void connection_dir_about_to_close(dir_connection_t *dir_conn); -void directory_initiate_command(const tor_addr_t *addr, - uint16_t or_port, uint16_t dir_port, +void directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, + const tor_addr_t *dir_addr, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, dir_indirection_t indirection, diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index e358e92ccd..583b7efa8b 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2117,8 +2117,17 @@ launch_direct_bridge_descriptor_fetch(bridge_info_t *bridge) return; } - directory_initiate_command(&bridge->addr, - bridge->port, 0/*no dirport*/, + /* Until we get a descriptor for the bridge, we only know one address for + * it. If we */ + if (!fascist_firewall_allows_address_addr(&bridge->addr, bridge->port, + FIREWALL_OR_CONNECTION, 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, diff --git a/src/or/router.c b/src/or/router.c index ba32d77fd1..d4131992aa 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -1244,14 +1244,15 @@ consider_testing_reachability(int test_or, int test_dir) extend_info_free(ei); } + /* XXX IPv6 self testing */ tor_addr_from_ipv4h(&addr, me->addr); if (test_dir && !check_whether_dirport_reachable() && !connection_get_by_type_addr_port_purpose( CONN_TYPE_DIR, &addr, me->dir_port, DIR_PURPOSE_FETCH_SERVERDESC)) { /* ask myself, via tor, for my server descriptor. */ - directory_initiate_command(&addr, - me->or_port, me->dir_port, + directory_initiate_command(&addr, me->or_port, + &addr, me->dir_port, me->cache_info.identity_digest, DIR_PURPOSE_FETCH_SERVERDESC, ROUTER_PURPOSE_GENERAL, From 4528f893163ad7ab27915451caf23b3a722413ce Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Sun, 3 Jan 2016 23:16:06 +1100 Subject: [PATCH 07/15] Make entry_guard_set_status consistent with entry_is_live Check fascist_firewall_allows_node in entry_guard_set_status and return the same message as entry_is_live. --- src/or/entrynodes.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/or/entrynodes.c b/src/or/entrynodes.c index 583b7efa8b..1ce44d1026 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -87,7 +87,7 @@ get_entry_guards(void) /** Check whether the entry guard e is usable, given the directory * authorities' opinion about the router (stored in ri) and the user's - * configuration (in options). Set e->bad_since + * configuration (in options). Set e->bad_since * accordingly. Return true iff the entry guard's status changes. * * If it's not usable, set *reason to a static string explaining why. @@ -117,6 +117,9 @@ entry_guard_set_status(entry_guard_t *e, const node_t *node, *reason = "not recommended as a guard"; else if (routerset_contains_node(options->ExcludeNodes, node)) *reason = "excluded"; + /* We only care about OR connection connectivity for entry guards. */ + else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) + *reason = "unreachable by config"; else if (e->path_bias_disabled) *reason = "path-biased"; From 3b8216f2155f224bf66497c71de4cecb55cd83e6 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Mon, 4 Jan 2016 00:35:22 +1100 Subject: [PATCH 08/15] Use fascist firewall and ClientUseIPv4 for bridge clients Bridge clients ignore ClientUseIPv6, acting as if it is always 1. This preserves existing behaviour. Make ClientPreferIPv6OR/DirPort auto by default: * Bridge clients prefer IPv6 by default. * Other clients prefer IPv4 by default. This preserves existing behaviour. --- doc/tor.1.txt | 16 +- src/or/config.c | 49 ++---- src/or/connection.c | 20 +-- src/or/directory.c | 12 +- src/or/nodelist.c | 156 +++++++------------ src/or/nodelist.h | 5 +- src/or/or.h | 10 +- src/or/policies.c | 296 ++++++++++++++++--------------------- src/or/policies.h | 17 +-- src/or/routerlist.c | 8 +- src/test/test_entrynodes.c | 45 +++++- src/test/test_policy.c | 152 ++++++++++++++----- 12 files changed, 402 insertions(+), 384 deletions(-) diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 26abef1b91..f201a6169d 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1500,19 +1500,21 @@ The following options are useful only for clients (that is, if in a **Bridge**, proxy, or pluggable transport line will try connecting over IPv6 even if **ClientUseIPv6** is set to 0. (Default: 0) -[[ClientPreferIPv6DirPort]] **ClientPreferIPv6DirPort** **0**|**1**:: +[[ClientPreferIPv6DirPort]] **ClientPreferIPv6DirPort** **0**|**1**|**auto**:: If this option is set to 1, Tor prefers a directory port with an IPv6 address over one with IPv4, for direct connections, if a given directory server has both. (Tor also prefers an IPv6 DirPort if IPv4Client is set to - 0.) Other things may influence the choice. This option breaks a tie to the - favor of IPv6. (Default: 0) + 0.) If this option is set to auto, Tor bridge clients prefer IPv6, and + other clients prefer IPv4. Other things may influence the choice. This + option breaks a tie to the favor of IPv6. (Default: auto) -[[ClientPreferIPv6ORPort]] **ClientPreferIPv6ORPort** **0**|**1**:: +[[ClientPreferIPv6ORPort]] **ClientPreferIPv6ORPort** **0**|**1**|**auto**:: 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. (Tor also - prefers an IPv6 ORPort if IPv4Client is set to 0.) Other things may - influence the choice. This option breaks a tie to the favor of IPv6. - (Default: 0) + prefers an IPv6 ORPort if IPv4Client is set to 0.) If this option is set + to auto, Tor bridge clients prefer IPv6, and other clients prefer IPv4. + Other things may influence the choice. This option breaks a tie to the + favor of IPv6. (Default: auto) [[PathsNeededToBuildCircuits]] **PathsNeededToBuildCircuits** __NUM__:: Tor clients don't build circuits for user traffic until they know diff --git a/src/or/config.c b/src/or/config.c index d676c6e29d..caa01d1d93 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -190,8 +190,8 @@ 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(ClientPreferIPv6DirPort, BOOL, "0"), + V(ClientPreferIPv6ORPort, AUTOBOOL, "auto"), + V(ClientPreferIPv6DirPort, AUTOBOOL, "auto"), V(ClientRejectInternalAddresses, BOOL, "1"), V(ClientTransportPlugin, LINELIST, NULL), V(ClientUseIPv6, BOOL, "0"), @@ -3073,9 +3073,8 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - /* Terminate Reachable*Addresses with reject *, but check if it has an - * IPv6 entry on the way through */ - int reachable_knows_ipv6 = 0; + /* Terminate Reachable*Addresses with reject * + */ for (i=0; i<3; i++) { config_line_t **linep = (i==0) ? &options->ReachableAddresses : @@ -3085,20 +3084,6 @@ options_validate(or_options_t *old_options, or_options_t *options, continue; /* We need to end with a reject *:*, not an implicit accept *:* */ for (;;) { - /* Check if the policy has an IPv6 entry, or uses IPv4-specific - * policies (and therefore we assume it's aware of IPv6). */ - if (!strcmpstart((*linep)->value, "accept6") || - !strcmpstart((*linep)->value, "reject6") || - !strstr((*linep)->value, "*6") || - strchr((*linep)->value, '[') || - !strcmpstart((*linep)->value, "accept4") || - !strcmpstart((*linep)->value, "reject4") || - !strstr((*linep)->value, "*4")) - reachable_knows_ipv6 = 1; - /* already has a reject all */ - if (!strcmp((*linep)->value, "reject *:*") || - !strcmp((*linep)->value, "reject *")) - break; linep = &((*linep)->next); if (!*linep) { *linep = tor_malloc_zero(sizeof(config_line_t)); @@ -3112,18 +3097,6 @@ options_validate(or_options_t *old_options, or_options_t *options, } } - if (options->ClientUseIPv6 && - (options->ReachableAddresses || - options->ReachableORAddresses || - options->ReachableDirAddresses) && - !reachable_knows_ipv6) - log_warn(LD_CONFIG, "You have set ClientUseIPv6 1 and at least one of " - "ReachableAddresses, ReachableORAddresses, or " - "ReachableDirAddresses, but without any IPv6-specific rules. " - "Tor won't connect to any IPv6 addresses, unless a rule accepts " - "them. (Use 'accept6 *:*' or 'reject6 *:*' as the last rule to " - "disable this warning.)"); - if ((options->ReachableAddresses || options->ReachableORAddresses || options->ReachableDirAddresses || @@ -3135,18 +3108,20 @@ options_validate(or_options_t *old_options, or_options_t *options, /* We check if Reachable*Addresses blocks all addresses in * parse_reachable_addresses(). */ - if (options->ClientUseIPv4 == 0 && options->ClientUseIPv6 == 0) + if (options->ClientUseIPv4 == 0 && !fascist_firewall_use_ipv6(options)) REJECT("Tor cannot connect to the Internet if ClientUseIPv4 is 0 and " "ClientUseIPv6 is 0. Please set at least one of these options " - "to 1."); + "to 1, or configure bridges."); - if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6ORPort == 1) + if (!fascist_firewall_use_ipv6(options) + && options->ClientPreferIPv6ORPort == 1) log_warn(LD_CONFIG, "ClientPreferIPv6ORPort 1 is ignored unless " - "ClientUseIPv6 is also 1."); + "ClientUseIPv6 is also 1, or bridges are configured."); - if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6DirPort == 1) + if (!fascist_firewall_use_ipv6(options) + && options->ClientPreferIPv6DirPort == 1) log_warn(LD_CONFIG, "ClientPreferIPv6DirPort 1 is ignored unless " - "ClientUseIPv6 is also 1."); + "ClientUseIPv6 is also 1, or bridges are configured."); if (options->UseBridges && server_mode(options)) diff --git a/src/or/connection.c b/src/or/connection.c index f252d2faa1..0420f2656b 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1722,7 +1722,7 @@ connection_connect_sockaddr,(connection_t *conn, return inprogress ? 0 : 1; } -/* Log a message if connection violates ClientUseIPv4 0 or ClientUseIPv6 0. +/* Log a message if connection attempt is made when IPv4 or IPv6 is disabled. * Log a less severe message if we couldn't conform to ClientPreferIPv6ORPort * or ClientPreferIPv6ORPort. */ static void @@ -1730,9 +1730,9 @@ connection_connect_log_client_use_ip_version(const connection_t *conn) { const or_options_t *options = get_options(); - /* Only non-bridge clients care about ClientUseIPv4/6, bail out early on - * servers and bridge clients */ - if (options->UseBridges || server_mode(options) || !conn + /* Only clients care about ClientUseIPv4/6, bail out early on servers, and + * on connections we don't care about */ + if (server_mode(options) || !conn || conn->type == CONN_TYPE_EXIT) { return; } @@ -1742,11 +1742,11 @@ connection_connect_log_client_use_ip_version(const connection_t *conn) return; } - const int must_ipv4 = (options->ClientUseIPv6 == 0); + const int must_ipv4 = !fascist_firewall_use_ipv6(options); const int must_ipv6 = (options->ClientUseIPv4 == 0); const int pref_ipv6 = (conn->type == CONN_TYPE_OR - ? nodelist_prefer_ipv6_orport(options) - : nodelist_prefer_ipv6_dirport(options)); + ? fascist_firewall_prefer_ipv6_orport(options) + : fascist_firewall_prefer_ipv6_dirport(options)); tor_addr_t real_addr; tor_addr_make_null(&real_addr, AF_UNSPEC); @@ -1773,12 +1773,14 @@ connection_connect_log_client_use_ip_version(const connection_t *conn) if ((!pref_ipv6 && tor_addr_family(&real_addr) == AF_INET6) || (pref_ipv6 && tor_addr_family(&real_addr) == AF_INET)) { log_info(LD_NET, "Connection to %s doesn't satisfy ClientPreferIPv6%sPort " - "%d, with ClientUseIPv4 %d and ClientUseIPv6 %d.", + "%d, with ClientUseIPv4 %d, and fascist_firewall_use_ipv6 %d " + "(ClientUseIPv6 %d and UseBridges %d).", fmt_addr(&real_addr), conn->type == CONN_TYPE_OR ? "OR" : "Dir", conn->type == CONN_TYPE_OR ? options->ClientPreferIPv6ORPort : options->ClientPreferIPv6DirPort, - options->ClientUseIPv4, options->ClientUseIPv4); + options->ClientUseIPv4, fascist_firewall_use_ipv6(options), + options->ClientUseIPv6, options->UseBridges); } } diff --git a/src/or/directory.c b/src/or/directory.c index 665ba27767..b3a2f36f20 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -730,7 +730,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, * directory server, we have selected a server that has at least one address * allowed by ClientUseIPv4/6 and Reachable{"",OR,Dir}Addresses. This * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if - * possible. (If UseBridges is set, clients ignore all these settings.) + * possible. (If UseBridges is set, clients always use IPv6, and prefer it + * by default.) * * Now choose an address that we can use to connect to the directory server. */ @@ -1070,6 +1071,15 @@ directory_initiate_command_rend(const tor_addr_port_t *or_addr_port, log_debug(LD_DIR, "anonymized %d, use_begindir %d.", anonymized_connection, use_begindir); + if (!dir_port && !use_begindir) { + char ipaddr[TOR_ADDR_BUF_LEN]; + tor_addr_to_str(ipaddr, &addr, TOR_ADDR_BUF_LEN, 0); + log_warn(LD_BUG, "Cannot use directory server without dirport or " + "begindir! Address: %s, DirPort: %d, Connection Port: %d", + escaped_safe_str_client(ipaddr), dir_port, port); + return; + } + log_debug(LD_DIR, "Initiating %s", dir_conn_purpose_to_string(dir_purpose)); #ifndef NON_ANONYMOUS_MODE_ENABLED diff --git a/src/or/nodelist.c b/src/or/nodelist.c index 7ca1146e86..c8f93bf30c 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -214,76 +214,6 @@ nodelist_add_microdesc(microdesc_t *md) return node; } -/** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and - * ClientPreferIPv6DirPort? - * If we're unsure, return -1, otherwise, return 1 for IPv6 and 0 for IPv4. - */ -static int -nodelist_prefer_ipv6(const or_options_t *options) -{ - /* - Cheap implementation of config options ClientUseIPv4 & ClientUseIPv6 -- - If we're a server, use IPv4. - If we're a client running with bridges, use IPv6. - Otherwise, use IPv6 if we can and it's preferred, or if IPv4 is disabled. - See #4455 and #17840 for more on this subject. - */ - - /* Servers prefer IPv4 */ - if (server_mode(options)) { - return 0; - } - - /* Bridge clients prefer IPv6 */ - if (options->UseBridges) { - return 1; - } - - if (!options->ClientUseIPv4) { - return 1; - } - - return -1; -} - -/** Do we prefer to connect to IPv6 ORPorts? - */ -int -nodelist_prefer_ipv6_orport(const or_options_t *options) -{ - int pref_ipv6 = nodelist_prefer_ipv6(options); - - if (pref_ipv6 >= 0) { - return pref_ipv6; - } - - /* We prefer IPv6 ORPorts if the option is set */ - if (options->ClientUseIPv6 && options->ClientPreferIPv6ORPort) { - return 1; - } - - return 0; -} - -/** Do we prefer to connect to IPv6 DirPorts? - */ -int -nodelist_prefer_ipv6_dirport(const or_options_t *options) -{ - int pref_ipv6 = nodelist_prefer_ipv6(options); - - if (pref_ipv6 >= 0) { - return pref_ipv6; - } - - /* We prefer IPv6 DirPorts if the option is set */ - if (options->ClientUseIPv6 && options->ClientPreferIPv6DirPort) { - return 1; - } - - return 0; -} - /** Tell the nodelist that the current usable consensus is ns. * This makes the nodelist change all of the routerstatus entries for * the nodes, drop nodes that no longer have enough info to get used, @@ -330,7 +260,7 @@ nodelist_set_consensus(networkstatus_t *ns) node->is_bad_exit = rs->is_bad_exit; node->is_hs_dir = rs->is_hs_dir; node->ipv6_preferred = 0; - if (nodelist_prefer_ipv6_orport(options) && + if (fascist_firewall_prefer_ipv6_orport(options) && (tor_addr_is_null(&rs->ipv6_addr) == 0 || (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0))) node->ipv6_preferred = 1; @@ -916,9 +846,13 @@ node_get_addr(const node_t *node, tor_addr_t *addr_out) uint32_t node_get_prim_addr_ipv4h(const node_t *node) { - if (node->ri) { + /* Don't check the ORPort or DirPort, as this function isn't port-specific, + * and the node might have a valid IPv4 address, yet have a zero + * ORPort or DirPort. + */ + if (node->ri && tor_addr_is_valid_ipv4h(node->ri->addr, 0)) { return node->ri->addr; - } else if (node->rs) { + } else if (node->rs && tor_addr_is_valid_ipv4h(node->rs->addr, 0)) { return node->rs->addr; } return 0; @@ -994,20 +928,44 @@ node_get_declared_family(const node_t *node) return NULL; } -/* Does this node have a valid IPv6 address? */ -static int +/* Does this node have a valid IPv6 address? + * Prefer node_has_ipv6_orport() or node_has_ipv6_dirport() for + * checking specific ports. */ +int node_has_ipv6_addr(const node_t *node) { - 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); + /* Don't check the ORPort or DirPort, as this function isn't port-specific, + * and the node might have a valid IPv6 address, yet have a zero + * ORPort or DirPort. + */ + if (node->ri && tor_addr_is_valid(&node->ri->ipv6_addr, 0)) + return 1; + if (node->rs && tor_addr_is_valid(&node->rs->ipv6_addr, 0)) + return 1; + if (node->md && tor_addr_is_valid(&node->md->ipv6_addr, 0)) + return 1; return 0; } +/* Does this node have a valid IPv6 ORPort? */ +int +node_has_ipv6_orport(const node_t *node) +{ + tor_addr_port_t ipv6_orport; + node_get_pref_ipv6_orport(node, &ipv6_orport); + return tor_addr_port_is_valid_ap(&ipv6_orport, 0); +} + +/* Does this node have a valid IPv6 DirPort? */ +int +node_has_ipv6_dirport(const node_t *node) +{ + tor_addr_port_t ipv6_dirport; + node_get_pref_ipv6_dirport(node, &ipv6_dirport); + return tor_addr_port_is_valid_ap(&ipv6_dirport, 0); +} + /** Return 1 if we prefer the IPv6 address and OR TCP port of * node, else 0. * @@ -1028,20 +986,18 @@ node_ipv6_or_preferred(const node_t *node) tor_addr_port_t ipv4_addr; node_assert_ok(node); - if (!options->ClientUseIPv6) { + if (!fascist_firewall_use_ipv6(options)) { return 0; } else if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr) - || nodelist_prefer_ipv6_orport(get_options())) { - return node_has_ipv6_addr(node); + || fascist_firewall_prefer_ipv6_orport(get_options())) { + return node_has_ipv6_orport(node); } return 0; } #define RETURN_IPV4_AP(r, port_field, ap_out) \ STMT_BEGIN \ - if (r) { \ - if ((r)->addr == 0 || (r)->port_field == 0) \ - return -1; \ + if (r && tor_addr_port_is_valid_ipv4h((r)->addr, (r)->port_field, 0)) { \ tor_addr_from_ipv4h(&(ap_out)->addr, (r)->addr); \ (ap_out)->port = (r)->port_field; \ return 0; \ @@ -1091,16 +1047,16 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out) * fascist_firewall_* functions. Also check if the address or port are valid, * and try another alternative if they are not. */ - if (node->ri && node->ri->ipv6_orport - && !tor_addr_is_null(&node->ri->ipv6_addr)) { + if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr, + node->ri->ipv6_orport, 0)) { tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr); ap_out->port = node->ri->ipv6_orport; - } else if (node->rs && node->rs->ipv6_orport - && !tor_addr_is_null(&node->rs->ipv6_addr)) { + } else if (node->rs && tor_addr_port_is_valid(&node->rs->ipv6_addr, + node->rs->ipv6_orport, 0)) { tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); ap_out->port = node->rs->ipv6_orport; - } else if (node->md && node->md->ipv6_orport - && !tor_addr_is_null(&node->md->ipv6_addr)) { + } else if (node->md && tor_addr_port_is_valid(&node->md->ipv6_addr, + node->md->ipv6_orport, 0)) { tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr); ap_out->port = node->md->ipv6_orport; } else { @@ -1129,11 +1085,11 @@ node_ipv6_dir_preferred(const node_t *node) tor_addr_port_t ipv4_addr; node_assert_ok(node); - if (!options->ClientUseIPv6) { + if (!fascist_firewall_use_ipv6(options)) { return 0; } else if (node->ipv6_preferred || node_get_prim_dirport(node, &ipv4_addr) - || nodelist_prefer_ipv6_dirport(get_options())) { - return node_has_ipv6_addr(node); + || fascist_firewall_prefer_ipv6_dirport(get_options())) { + return node_has_ipv6_dirport(node); } return 0; } @@ -1183,12 +1139,12 @@ node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out) * they are not. Note that microdescriptors have no dir_port. */ /* Assume IPv4 and IPv6 dirports are the same */ - if (node->ri && node->ri->dir_port - && !tor_addr_is_null(&node->ri->ipv6_addr)) { + if (node->ri && tor_addr_port_is_valid(&node->ri->ipv6_addr, + node->ri->dir_port, 0)) { tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr); ap_out->port = node->ri->dir_port; - } else if (node->rs && node->rs->dir_port - && !tor_addr_is_null(&node->rs->ipv6_addr)) { + } else if (node->rs && tor_addr_port_is_valid(&node->rs->ipv6_addr, + node->rs->dir_port, 0)) { tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr); ap_out->port = node->rs->dir_port; } else { diff --git a/src/or/nodelist.h b/src/or/nodelist.h index fa1f22e630..8271e7532a 100644 --- a/src/or/nodelist.h +++ b/src/or/nodelist.h @@ -21,8 +21,6 @@ MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest)); const node_t *node_get_by_hex_id(const char *identity_digest); node_t *nodelist_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out); node_t *nodelist_add_microdesc(microdesc_t *md); -int nodelist_prefer_ipv6_orport(const or_options_t *options); -int nodelist_prefer_ipv6_dirport(const or_options_t *options); void nodelist_set_consensus(networkstatus_t *ns); void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md); @@ -58,6 +56,9 @@ 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_has_ipv6_addr(const node_t *node); +int node_has_ipv6_orport(const node_t *node); +int node_has_ipv6_dirport(const node_t *node); /* Deprecated - use node_ipv6_or_preferred or node_ipv6_dir_preferred */ #define node_ipv6_preferred(node) node_ipv6_or_preferred(node) int node_ipv6_or_preferred(const node_t *node); diff --git a/src/or/or.h b/src/or/or.h index 412789cafe..79b1c95ff0 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -4066,14 +4066,18 @@ typedef struct { * connecting over IPv4. We enforce this for OR and Dir connections. */ int ClientUseIPv4; /** If true, clients may connect over IPv6. If false, they will avoid - * connecting over IPv4. We enforce this for OR and Dir connections. */ + * connecting over IPv4. We enforce this for OR and Dir connections. + * Use fascist_firewall_use_ipv6() instead of accessing this value + * directly. */ int ClientUseIPv6; /** If true, prefer an IPv6 OR port over an IPv4 one for entry node - * connections. Use nodelist_prefer_ipv6_orport() instead of accessing + * connections. If auto, bridge clients prefer IPv6, and other clients + * prefer IPv4. Use fascist_firewall_prefer_ipv6_orport() instead of accessing * this value directly. */ int ClientPreferIPv6ORPort; /** If true, prefer an IPv6 directory port over an IPv4 one for direct - * directory connections. Use nodelist_prefer_ipv6_dirport() instead of + * directory connections. If auto, bridge clients prefer IPv6, and other + * clients prefer IPv4. Use fascist_firewall_prefer_ipv6_dirport() instead of * accessing this value directly. */ int ClientPreferIPv6DirPort; diff --git a/src/or/policies.c b/src/or/policies.c index 2a97df7cac..ecc89da1c2 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -272,8 +272,8 @@ parse_reachable_addresses(void) ret = -1; } - /* XX/teor - we ignore ReachableAddresses for bridge clients and relays */ - if (!options->UseBridges || server_mode(options)) { + /* We ignore ReachableAddresses for relays */ + if (!server_mode(options)) { if ((reachable_or_addr_policy && policy_is_reject_star(reachable_or_addr_policy, AF_UNSPEC)) || (reachable_dir_addr_policy @@ -282,12 +282,45 @@ parse_reachable_addresses(void) "ReachableAddresses, ReachableORAddresses, or " "ReachableDirAddresses reject all addresses. Please accept " "some addresses in these options."); + } else if (options->ClientUseIPv4 == 1 + && ((reachable_or_addr_policy + && policy_is_reject_star(reachable_or_addr_policy, AF_INET)) + || (reachable_dir_addr_policy + && policy_is_reject_star(reachable_dir_addr_policy, AF_INET)))) { + log_warn(LD_CONFIG, "You have set ClientUseIPv4 1, but " + "ReachableAddresses, ReachableORAddresses, or " + "ReachableDirAddresses reject all IPv4 addresses. " + "Tor will not connect using IPv4."); + } else if (fascist_firewall_use_ipv6(options) + && ((reachable_or_addr_policy + && policy_is_reject_star(reachable_or_addr_policy, AF_INET6)) + || (reachable_dir_addr_policy + && policy_is_reject_star(reachable_dir_addr_policy, AF_INET6)))) { + log_warn(LD_CONFIG, "You have configured tor to use IPv6 " + "(ClientUseIPv6 1 or UseBridges 1), but " + "ReachableAddresses, ReachableORAddresses, or " + "ReachableDirAddresses reject all IPv6 addresses. " + "Tor will not connect using IPv6."); } } return ret; } +/** Return true iff the firewall options, including ClientUseIPv4 0 and + * ClientUseIPv6 0, might block any address:port combination. + */ +int +firewall_is_fascist_or(void) +{ + const or_options_t *options = get_options(); + /* Assume every non-bridge relay has an IPv4 address. + * Clients which use bridges may only know the IPv6 address of their + * bridge. */ + return (reachable_or_addr_policy != NULL || options->ClientUseIPv4 == 0 + || (options->ClientUseIPv6 == 0 && options->UseBridges == 1)); +} + /** Return true iff policy (possibly NULL) will allow a * connection to addr:port. */ @@ -326,14 +359,14 @@ addr_policy_permits_address(uint32_t addr, uint16_t port, /** Return true iff we think our firewall will let us make a connection to * addr:port. * - * If UseBridges is set, or we are configured as a server, ignore the - * following address family preferences. + * If we are configured as a server, ignore any address family preference and + * just use IPv4. * Otherwise: * - return false for all IPv4 addresses: * - if ClientUseIPv4 is 0, or * if pref_only and pref_ipv6 are both true; * - return false for all IPv6 addresses: - * - if ClientUseIPv6 is 0, or + * - if fascist_firewall_use_ipv6() is 0, or * - if pref_only is true and pref_ipv6 is false. * * Return false if addr is NULL or tor_addr_is_null(), or if port is 0. */ @@ -349,13 +382,14 @@ fascist_firewall_allows_address(const tor_addr_t *addr, return 0; } - if (!options->UseBridges && !server_mode(options)) { + if (!server_mode(options)) { if (tor_addr_family(addr) == AF_INET && (!options->ClientUseIPv4 || (pref_only && pref_ipv6))) return 0; + /* Bridges can always use IPv6 */ if (tor_addr_family(addr) == AF_INET6 && - (!options->ClientUseIPv6 || (pref_only && !pref_ipv6))) + (!fascist_firewall_use_ipv6(options) || (pref_only && !pref_ipv6))) return 0; } @@ -363,6 +397,87 @@ fascist_firewall_allows_address(const tor_addr_t *addr, firewall_policy); } +/** Is this client configured to use IPv6? + * Clients use IPv6 if ClientUseIPv6 is 1, or UseBridges is 1. + */ +int fascist_firewall_use_ipv6(const or_options_t *options) +{ + return (options->ClientUseIPv6 == 1 || options->UseBridges == 1); +} + +/** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and + * ClientPreferIPv6DirPort? + * If we're unsure, return -1, otherwise, return 1 for IPv6 and 0 for IPv4. + */ +static int +fascist_firewall_prefer_ipv6_impl(const or_options_t *options) +{ + /* + Cheap implementation of config options ClientUseIPv4 & ClientUseIPv6 -- + If we're a server or IPv6 is disabled, use IPv4. + If IPv4 is disabled, use IPv6. + */ + + if (server_mode(options) || !fascist_firewall_use_ipv6(options)) { + return 0; + } + + if (!options->ClientUseIPv4) { + return 1; + } + + return -1; +} + +/** Do we prefer to connect to IPv6 ORPorts? + */ +int +fascist_firewall_prefer_ipv6_orport(const or_options_t *options) +{ + int pref_ipv6 = fascist_firewall_prefer_ipv6_impl(options); + + if (pref_ipv6 >= 0) { + return pref_ipv6; + } + + /* We can use both IPv4 and IPv6 - which do we prefer? */ + if (options->ClientPreferIPv6ORPort == 1) { + return 1; + } + + /* For bridge clients, ClientPreferIPv6ORPort auto means "prefer IPv6". */ + if (options->UseBridges && options->ClientPreferIPv6ORPort != 0) { + return 1; + } + + return 0; +} + +/** Do we prefer to connect to IPv6 DirPorts? + */ +int +fascist_firewall_prefer_ipv6_dirport(const or_options_t *options) +{ + int pref_ipv6 = fascist_firewall_prefer_ipv6_impl(options); + + if (pref_ipv6 >= 0) { + return pref_ipv6; + } + + /* We can use both IPv4 and IPv6 - which do we prefer? */ + if (options->ClientPreferIPv6DirPort == 1) { + return 1; + } + + /* For bridge clients, ClientPreferIPv6ORPort auto means "prefer IPv6". + * XX/teor - do bridge clients ever use a DirPort? */ + if (options->UseBridges && options->ClientPreferIPv6DirPort != 0) { + return 1; + } + + return 0; +} + /** Return true iff we think our firewall will let us make a connection to * addr:port. Uses ReachableORAddresses or ReachableDirAddresses based on * fw_connection. @@ -380,12 +495,12 @@ fascist_firewall_allows_address_addr(const tor_addr_t *addr, uint16_t port, return fascist_firewall_allows_address(addr, port, reachable_or_addr_policy, pref_only, - nodelist_prefer_ipv6_orport(options)); + fascist_firewall_prefer_ipv6_orport(options)); } else if (fw_connection == FIREWALL_DIR_CONNECTION) { return fascist_firewall_allows_address(addr, port, reachable_dir_addr_policy, pref_only, - nodelist_prefer_ipv6_dirport(options)); + fascist_firewall_prefer_ipv6_dirport(options)); } else { log_warn(LD_BUG, "Bad firewall_connection_t value %d.", fw_connection); @@ -478,30 +593,6 @@ fascist_firewall_allows_ri_impl(const routerinfo_t *ri, ri->dir_port, fw_connection, pref_only); } -/** Return true iff we think our firewall will let us make a connection to - * ri on either its IPv4 or IPv6 address. Uses - * or_port/ipv6_orport/ReachableORAddresses or dir_port/ReachableDirAddresses - * based on IPv4/IPv6 and fw_connection. - * If pref_only, return false if addr is not in the client's preferred address - * family. - * Consults the corresponding node if the addresses in ri are not permitted. */ -int -fascist_firewall_allows_ri(const routerinfo_t *ri, - firewall_connection_t fw_connection, int pref_only) -{ - if (!ri) { - return 0; - } - - /* Assume IPv4 and IPv6 DirPorts are the same */ - if (fascist_firewall_allows_ri_impl(ri, fw_connection, pref_only)) { - return 1; - } else { - const node_t *node = node_get_by_id(ri->cache_info.identity_digest); - return fascist_firewall_allows_node(node, fw_connection, pref_only); - } -} - /** Like fascist_firewall_allows_rs, but doesn't consult the node. */ static int fascist_firewall_allows_rs_impl(const routerstatus_t *rs, @@ -562,40 +653,6 @@ fascist_firewall_allows_md_impl(const microdesc_t *md, fw_connection, pref_only); } -/** Return true iff we think our firewall will let us make a connection to - * md on its IPv6 address. (The IPv4 address is in the routerstatus and - * the routerinfo.) Uses ipv6_orport/ReachableORAddresses or - * dir_port/ReachableDirAddresses based on fw_connection. - * If pref_only, return false if addr is not in the client's preferred address - * family. - * Consults the corresponding node if the address in md is not permitted. */ -int -fascist_firewall_allows_md(const microdesc_t *md, - firewall_connection_t fw_connection, - int pref_only) -{ - if (!md) { - return 0; - } - - if (fascist_firewall_allows_md_impl(md, fw_connection, pref_only)) { - return 1; - } else { - networkstatus_t *ns; - ns = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); - if (!ns) { - return 0; - } - const routerstatus_t *rs; - rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); - if (!rs) { - return 0; - } - const node_t *node = node_get_by_id(rs->identity_digest); - return fascist_firewall_allows_node(node, fw_connection, pref_only); - } -} - /** Return true iff we think our firewall will let us make a connection to * node: * - if preferred is true, on its preferred address, @@ -816,44 +873,6 @@ fascist_firewall_choose_address_ipv4h(uint32_t ipv4h_addr, } \ STMT_END -/** Copy an address and port from ri into ap that we think our - * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and - * ipv4_orport/ipv6_orport/ReachableORAddresses or - * ipv4_dirport/ipv6_dirport/ReachableDirAddresses based on IPv4/IPv6 and - * fw_connection. - * If pref_only, only choose preferred addresses. In either case, choose - * a preferred address before an address that's not preferred. - * If neither address is chosen, return 0, else return 1. - * Consults the corresponding node if the addresses in ri are not valid. */ -int -fascist_firewall_choose_address_ri(const routerinfo_t *ri, - firewall_connection_t fw_connection, - int pref_only, tor_addr_port_t* ap) -{ - if (!ri) { - return 0; - } - - tor_assert(ap); - - /* Don't do the lookup if the IPv6 address/port in ri is OK. - * If it's OK, assume the dir_port is also OK. */ - tor_addr_port_t ipv6_or_ap; - IPV6_OR_LOOKUP(ri, ri->cache_info.identity_digest, ipv6_or_ap); - - /* Assume IPv4 and IPv6 DirPorts are the same. - * Assume the IPv6 OR and Dir addresses are the same. */ - return fascist_firewall_choose_address_ipv4h(ri->addr, - ri->or_port, - ri->dir_port, - &ipv6_or_ap.addr, - ipv6_or_ap.port, - ri->dir_port, - fw_connection, - pref_only, - ap); -} - /** Copy an address and port from rs into ap that we think our * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and * ipv4_orport/ipv6_orport/ReachableORAddresses or @@ -892,73 +911,6 @@ fascist_firewall_choose_address_rs(const routerstatus_t *rs, ap); } -/* Copy the IPv6 address and ORPort from md into ap if we think - * our firewall will let us connect to it. Uses ReachableORAddresses. - * If pref_only, only copy if it's a preferred address. - * If fw_connection is FIREWALL_DIR_CONNECTION, don't copy the address. - * If the address isn't copied, return 0, else return 1. */ -static int -fascist_firewall_choose_address_md_impl(const microdesc_t *md, - firewall_connection_t fw_connection, - int pref_only, tor_addr_port_t* ap) -{ - if (!md) { - return 0; - } - - /* Can't check dirport, it doesn't have one */ - if (fw_connection == FIREWALL_DIR_CONNECTION) { - return 0; - } - - tor_assert(ap); - - if (fascist_firewall_allows_md(md, fw_connection, pref_only)) { - tor_addr_copy(&ap->addr, &md->ipv6_addr); - ap->port = md->ipv6_orport; - return 1; - } else { - return 0; - } -} - -/** Lookup the node for md, and call fascist_firewall_choose_address_node on - * it. If any step in this process fails, fall back to calling - * fascist_firewall_choose_address_md_impl. */ -int -fascist_firewall_choose_address_md(const microdesc_t *md, - firewall_connection_t fw_connection, - int pref_only, tor_addr_port_t* ap) -{ - if (!md) { - return 0; - } - - tor_assert(ap); - - /* If we can't get the node, */ - networkstatus_t *ns; - ns = networkstatus_get_latest_consensus_by_flavor(FLAV_MICRODESC); - if (!ns) { - return fascist_firewall_choose_address_md_impl(md, fw_connection, - pref_only, ap); - } - const routerstatus_t *rs; - rs = router_get_consensus_status_by_descriptor_digest(ns, md->digest); - if (!rs) { - return fascist_firewall_choose_address_md_impl(md, fw_connection, - pref_only, ap); - } - const node_t *node = node_get_by_id(rs->identity_digest); - if (node) { - return fascist_firewall_choose_address_node(node, fw_connection, - pref_only, ap); - } else { - return fascist_firewall_choose_address_md_impl(md, fw_connection, - pref_only, ap); - } -} - /** Copy an address and port from node into ap that we think our * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and * ipv4_orport/ipv6_orport/ReachableORAddresses or diff --git a/src/or/policies.h b/src/or/policies.h index 7309bcf667..ac4f7ea492 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -29,6 +29,11 @@ typedef enum firewall_connection_t { typedef int exit_policy_parser_cfg_t; +int firewall_is_fascist_or(void); +int fascist_firewall_use_ipv6(const or_options_t *options); +int fascist_firewall_prefer_ipv6_orport(const or_options_t *options); +int fascist_firewall_prefer_ipv6_dirport(const or_options_t *options); + int fascist_firewall_allows_address_addr(const tor_addr_t *addr, uint16_t port, firewall_connection_t fw_connection, int pref_only); @@ -39,15 +44,9 @@ int fascist_firewall_allows_address_ipv4h(uint32_t ipv4h_or_addr, uint16_t ipv4_or_port, firewall_connection_t fw_connection, int pref_only); -int fascist_firewall_allows_ri(const routerinfo_t *ri, - firewall_connection_t fw_connection, - int pref_only); int fascist_firewall_allows_rs(const routerstatus_t *rs, firewall_connection_t fw_connection, int pref_only); -int fascist_firewall_allows_md(const microdesc_t *md, - firewall_connection_t fw_connection, - int pref_only); int fascist_firewall_allows_node(const node_t *node, firewall_connection_t fw_connection, int pref_only); @@ -61,15 +60,9 @@ const tor_addr_port_t * fascist_firewall_choose_address( int want_a, firewall_connection_t fw_connection, int pref_only); -int fascist_firewall_choose_address_ri(const routerinfo_t *ri, - firewall_connection_t fw_connection, - int pref_only, tor_addr_port_t* ap); int fascist_firewall_choose_address_rs(const routerstatus_t *rs, firewall_connection_t fw_connection, int pref_only, tor_addr_port_t* ap); -int fascist_firewall_choose_address_md(const microdesc_t *md, - firewall_connection_t fw_connection, - int pref_only, tor_addr_port_t* ap); int fascist_firewall_choose_address_node(const node_t *node, firewall_connection_t fw_connection, int pref_only, tor_addr_port_t* ap); diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 2923da311f..247818d8e6 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1594,8 +1594,8 @@ router_picked_poor_directory_log(const routerstatus_t *rs) #define RETRY_ALTERNATE_IP_VERSION(retry_label) \ STMT_BEGIN \ if (result == NULL && try_ip_pref && options->ClientUseIPv4 \ - && options->ClientUseIPv6 && !server_mode(options) && n_not_preferred \ - && !n_busy) { \ + && fascist_firewall_use_ipv6(options) && !server_mode(options) \ + && n_not_preferred && !n_busy) { \ n_excluded = 0; \ n_busy = 0; \ try_ip_pref = 0; \ @@ -1976,7 +1976,7 @@ router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, continue; if (node_is_unreliable(node, need_uptime, need_capacity, need_guard)) continue; - /* Choose a node with a preferred OR address */ + /* Choose a node with an OR address that matches the firewall rules */ if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, pref_addr)) continue; @@ -2443,7 +2443,7 @@ node_sl_choose_by_bandwidth(const smartlist_t *sl, * If CRN_PREF_ADDR is set in flags, we only consider nodes that * have an address that is preferred by the ClientPreferIPv6ORPort setting * (regardless of this flag, we exclude nodes that aren't allowed by the - * firewall, including ClientUseIPv4 0 and ClientUseIPv6 0). + * firewall, including ClientUseIPv4 0 and fascist_firewall_use_ipv6() == 0). */ const node_t * router_choose_random_node(smartlist_t *excludedsmartlist, diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index e4947e0959..a0208b9cfc 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -107,6 +107,11 @@ test_choose_random_entry_no_guards(void *arg) chosen_entry = choose_random_entry(NULL); tt_assert(chosen_entry); + /* And with the preference on auto */ + mocked_options.ClientPreferIPv6ORPort = -1; + chosen_entry = choose_random_entry(NULL); + tt_assert(chosen_entry); + /* Check that we don't get a guard if it doesn't pass mandatory address * settings */ memset(&mocked_options, 0, sizeof(mocked_options)); @@ -128,6 +133,21 @@ test_choose_random_entry_no_guards(void *arg) chosen_entry = choose_random_entry(NULL); tt_assert(chosen_entry); + /* Check that we get a guard if it passes preferred address settings when + * they're auto */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientPreferIPv6ORPort = -1; + + chosen_entry = choose_random_entry(NULL); + tt_assert(chosen_entry); + + /* And with IPv6 active */ + mocked_options.ClientUseIPv6 = 1; + + chosen_entry = choose_random_entry(NULL); + tt_assert(chosen_entry); + done: memset(&mocked_options, 0, sizeof(mocked_options)); UNMOCK(get_options); @@ -166,6 +186,11 @@ test_choose_random_entry_one_possible_guard(void *arg) chosen_entry = choose_random_entry(NULL); tt_ptr_op(chosen_entry, OP_EQ, the_guard); + /* And with the preference on auto */ + mocked_options.ClientPreferIPv6ORPort = -1; + chosen_entry = choose_random_entry(NULL); + tt_ptr_op(chosen_entry, OP_EQ, the_guard); + /* Check that we don't get a guard if it doesn't pass mandatory address * settings */ memset(&mocked_options, 0, sizeof(mocked_options)); @@ -190,6 +215,21 @@ test_choose_random_entry_one_possible_guard(void *arg) * time, so we can't be sure we get the guard */ tt_assert(chosen_entry); + /* Check that we get the guard if it passes preferred address settings when + * they're auto */ + memset(&mocked_options, 0, sizeof(mocked_options)); + mocked_options.ClientUseIPv4 = 1; + mocked_options.ClientPreferIPv6ORPort = -1; + + chosen_entry = choose_random_entry(NULL); + tt_ptr_op(chosen_entry, OP_EQ, the_guard); + + /* and with IPv6 active */ + mocked_options.ClientUseIPv6 = 1; + + chosen_entry = choose_random_entry(NULL); + tt_ptr_op(chosen_entry, OP_EQ, the_guard); + done: memset(&mocked_options, 0, sizeof(mocked_options)); UNMOCK(get_options); @@ -722,8 +762,9 @@ test_node_preferred_orport(void *arg) /* Setup options */ memset(&mocked_options, 0, sizeof(mocked_options)); - /* We don't test ClientPreferIPv6ORPort here, because it's only used in - * nodelist_set_consensus to setup each node_t. */ + /* We don't test ClientPreferIPv6ORPort here, because it's used in + * nodelist_set_consensus to setup node.ipv6_preferred, which we set + * directly. */ MOCK(get_options, mock_get_options); /* Setup IP addresses */ diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 077d1b2af5..1daa38ecf2 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1235,10 +1235,42 @@ test_policies_fascist_firewall_allows_address(void *arg) /* Test the function's address matching with UseBridges on */ memset(&mock_options, 0, sizeof(or_options_t)); - mock_options.ClientUseIPv4 = 0; - mock_options.ClientUseIPv6 = 0; + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; mock_options.UseBridges = 1; + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) + == 0); + + /* Preferring IPv4 */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0) + == 1); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0) + == 0); + + /* Preferring IPv6 */ + tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1) + == 0); + tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1) + == 1); + tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1) + == 0); + tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1) + == 0); + + /* bridge clients always use IPv6, regardless of ClientUseIPv6 */ + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 0; tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) == 1); tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) @@ -1389,6 +1421,22 @@ test_policies_fascist_firewall_choose_address(void *arg) FIREWALL_DIR_CONNECTION, 1) == &ipv4_dir_ap); + /* Auto (Preferring IPv4) */ + mock_options.ClientPreferIPv6ORPort = -1; + mock_options.ClientPreferIPv6DirPort = -1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv4_dir_ap); + /* Preferring IPv6 */ mock_options.ClientPreferIPv6ORPort = 1; mock_options.ClientPreferIPv6DirPort = 1; @@ -1440,41 +1488,75 @@ test_policies_fascist_firewall_choose_address(void *arg) /* Choose an address with UseBridges on */ memset(&mock_options, 0, sizeof(or_options_t)); mock_options.UseBridges = 1; + mock_options.ClientUseIPv4 = 1; + mock_options.ClientUseIPv6 = 1; - for (mock_options.ClientUseIPv4 = 0; mock_options.ClientUseIPv4 <= 1; - mock_options.ClientUseIPv4++) { - for (mock_options.ClientUseIPv6 = 0; mock_options.ClientUseIPv6 <= 1; - mock_options.ClientUseIPv6++) { - for (mock_options.ClientPreferIPv6ORPort = 0; - mock_options.ClientPreferIPv6ORPort <= 1; - mock_options.ClientPreferIPv6ORPort++) { - for (mock_options.ClientPreferIPv6DirPort = 0; - mock_options.ClientPreferIPv6DirPort <= 1; - mock_options.ClientPreferIPv6DirPort++) { - /* This (ab)uses the actual enum values */ - tt_assert(FIREWALL_OR_CONNECTION < FIREWALL_DIR_CONNECTION); - for (firewall_connection_t fw_connection = FIREWALL_OR_CONNECTION; - fw_connection <= FIREWALL_DIR_CONNECTION; fw_connection++) { - for (int pref_only = 0; pref_only <= 1; pref_only++) { + /* Preferring IPv4 */ + mock_options.ClientPreferIPv6ORPort = 0; + mock_options.ClientPreferIPv6DirPort = 0; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv4_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv4_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv4_dir_ap); - /* Ignoring all other settings, want_a should choose the address - * for bridge clients */ - tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, - &ipv6_or_ap, 1, - fw_connection, - pref_only) - == &ipv4_or_ap); - tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, - &ipv6_or_ap, 0, - fw_connection, - pref_only) - == &ipv6_or_ap); - } - } - } - } - } - } + /* Auto (Preferring IPv6 for bridge clients) */ + mock_options.ClientPreferIPv6ORPort = -1; + mock_options.ClientPreferIPv6DirPort = -1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); + + /* Preferring IPv6 */ + mock_options.ClientPreferIPv6ORPort = 1; + mock_options.ClientPreferIPv6DirPort = 1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); + + + /* In the default configuration (Auto / IPv6 off), bridge clients should + * still use and prefer IPv6 regardless of ClientUseIPv6. */ + mock_options.ClientUseIPv6 = 0; + mock_options.ClientPreferIPv6ORPort = -1; + mock_options.ClientPreferIPv6DirPort = -1; + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 0) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, + FIREWALL_OR_CONNECTION, 1) + == &ipv6_or_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 0) + == &ipv6_dir_ap); + tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, + FIREWALL_DIR_CONNECTION, 1) + == &ipv6_dir_ap); /* Choose an address with IPv4 on */ memset(&mock_options, 0, sizeof(or_options_t)); From e991d642ec14d41df9da70442d99861bdb5bfb5b Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Thu, 21 Jan 2016 12:58:59 +1100 Subject: [PATCH 09/15] Add firewall_is_fascist_dir() Refactor common parts of firewall_is_fascist_or(). --- src/or/policies.c | 32 +++++++++++++++++++++++++------- src/or/policies.h | 1 + 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/src/or/policies.c b/src/or/policies.c index ecc89da1c2..506edec394 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -307,18 +307,36 @@ parse_reachable_addresses(void) return ret; } -/** Return true iff the firewall options, including ClientUseIPv4 0 and - * ClientUseIPv6 0, might block any address:port combination. - */ -int -firewall_is_fascist_or(void) +/* Return true iff ClientUseIPv4 0 or ClientUseIPv6 0 might block any OR or Dir + * address:port combination. */ +static int +firewall_is_fascist_impl(void) { const or_options_t *options = get_options(); /* Assume every non-bridge relay has an IPv4 address. * Clients which use bridges may only know the IPv6 address of their * bridge. */ - return (reachable_or_addr_policy != NULL || options->ClientUseIPv4 == 0 - || (options->ClientUseIPv6 == 0 && options->UseBridges == 1)); + return (options->ClientUseIPv4 == 0 + || (!fascist_firewall_use_ipv6(options) + && options->UseBridges == 1)); +} + +/** Return true iff the firewall options, including ClientUseIPv4 0 and + * ClientUseIPv6 0, might block any OR address:port combination. + */ +int +firewall_is_fascist_or(void) +{ + return (reachable_or_addr_policy != NULL || firewall_is_fascist_impl()); +} + +/** Return true iff the firewall options, including ClientUseIPv4 0 and + * ClientUseIPv6 0, might block any Dir address:port combination. + */ +int +firewall_is_fascist_dir(void) +{ + return (reachable_dir_addr_policy != NULL || firewall_is_fascist_impl()); } /** Return true iff policy (possibly NULL) will allow a diff --git a/src/or/policies.h b/src/or/policies.h index ac4f7ea492..65f10e2ed7 100644 --- a/src/or/policies.h +++ b/src/or/policies.h @@ -30,6 +30,7 @@ typedef enum firewall_connection_t { typedef int exit_policy_parser_cfg_t; int firewall_is_fascist_or(void); +int firewall_is_fascist_dir(void); int fascist_firewall_use_ipv6(const or_options_t *options); int fascist_firewall_prefer_ipv6_orport(const or_options_t *options); int fascist_firewall_prefer_ipv6_dirport(const or_options_t *options); From 772577b547aa5e6b13b89f465acf656cd08f2917 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Thu, 21 Jan 2016 13:30:57 +1100 Subject: [PATCH 10/15] Optimise reachability checks when iterating through relay lists Skip address checks on servers. Skip allowed-only address checks on non-bridge clients with IPv4. --- src/or/policies.c | 4 ++++ src/or/routerlist.c | 55 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/src/or/policies.c b/src/or/policies.c index 506edec394..0dc4f96c8b 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -323,6 +323,8 @@ firewall_is_fascist_impl(void) /** Return true iff the firewall options, including ClientUseIPv4 0 and * ClientUseIPv6 0, might block any OR address:port combination. + * Address preferences may still change which address is selected even if + * this function returns false. */ int firewall_is_fascist_or(void) @@ -332,6 +334,8 @@ firewall_is_fascist_or(void) /** Return true iff the firewall options, including ClientUseIPv4 0 and * ClientUseIPv6 0, might block any Dir address:port combination. + * Address preferences may still change which address is selected even if + * this function returns false. */ int firewall_is_fascist_dir(void) diff --git a/src/or/routerlist.c b/src/or/routerlist.c index 247818d8e6..9b8885e9c9 100644 --- a/src/or/routerlist.c +++ b/src/or/routerlist.c @@ -1623,6 +1623,30 @@ router_picked_poor_directory_log(const routerstatus_t *rs) } \ STMT_END +/* When iterating through the routerlist, can OR address/port preference + * and reachability checks be skipped? + */ +static int +router_skip_or_reachability(const or_options_t *options, int try_ip_pref) +{ + /* Servers always have and prefer IPv4. + * And if clients are checking against the firewall for reachability only, + * but there's no firewall, don't bother checking */ + return server_mode(options) || (!try_ip_pref && !firewall_is_fascist_or()); +} + +/* When iterating through the routerlist, can Dir address/port preference + * and reachability checks be skipped? + */ +static int +router_skip_dir_reachability(const or_options_t *options, int try_ip_pref) +{ + /* Servers always have and prefer IPv4. + * And if clients are checking against the firewall for reachability only, + * but there's no firewall, don't bother checking */ + return server_mode(options) || (!try_ip_pref && !firewall_is_fascist_dir()); +} + /** Pick a random running valid directory server/mirror from our * routerlist. Arguments are as for router_pick_directory_server(), except: * @@ -1661,6 +1685,9 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, overloaded_direct = smartlist_new(); overloaded_tunnel = smartlist_new(); + const int skip_or = router_skip_or_reachability(options, try_ip_pref); + const int skip_dir = router_skip_dir_reachability(options, try_ip_pref); + /* Find all the running dirservers we know about. */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { int is_trusted, is_trusted_extrainfo; @@ -1704,18 +1731,20 @@ router_pick_directory_server_impl(dirinfo_type_t type, int flags, is_overloaded = status->last_dir_503_at + DIR_503_TIMEOUT > now; - /* We use an IPv6 address if we have one and we prefer it. + /* Clients use IPv6 addresses if the server has one and the client + * prefers IPv6. * Add the router if its preferred address and port are reachable. * If we don't get any routers, we'll try again with the non-preferred * address for each router (if any). (To ensure correct load-balancing * we try routers that only have one address both times.) */ - if (!fascistfirewall || + if (!fascistfirewall || skip_or || fascist_firewall_allows_rs(status, FIREWALL_OR_CONNECTION, try_ip_pref)) smartlist_add(is_trusted ? trusted_tunnel : is_overloaded ? overloaded_tunnel : tunnel, (void*)node); - else if (fascist_firewall_allows_rs(status, FIREWALL_DIR_CONNECTION, + else if (skip_dir || + fascist_firewall_allows_rs(status, FIREWALL_DIR_CONNECTION, try_ip_pref)) smartlist_add(is_trusted ? trusted_direct : is_overloaded ? overloaded_direct : direct, (void*)node); @@ -1820,6 +1849,9 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, overloaded_direct = smartlist_new(); overloaded_tunnel = smartlist_new(); + const int skip_or = router_skip_or_reachability(options, try_ip_pref); + const int skip_dir = router_skip_dir_reachability(options, try_ip_pref); + SMARTLIST_FOREACH_BEGIN(sourcelist, const dir_server_t *, d) { int is_overloaded = @@ -1845,17 +1877,19 @@ router_pick_trusteddirserver_impl(const smartlist_t *sourcelist, continue; } - /* We use an IPv6 address if we have one and we prefer it. + /* Clients use IPv6 addresses if the server has one and the client + * prefers IPv6. * Add the router if its preferred address and port are reachable. * If we don't get any routers, we'll try again with the non-preferred * address for each router (if any). (To ensure correct load-balancing * we try routers that only have one address both times.) */ - if (!fascistfirewall || + if (!fascistfirewall || skip_or || fascist_firewall_allows_dir_server(d, FIREWALL_OR_CONNECTION, try_ip_pref)) smartlist_add(is_overloaded ? overloaded_tunnel : tunnel, (void*)d); - else if (fascist_firewall_allows_dir_server(d, FIREWALL_DIR_CONNECTION, + else if (skip_dir || + fascist_firewall_allows_dir_server(d, FIREWALL_DIR_CONNECTION, try_ip_pref)) smartlist_add(is_overloaded ? overloaded_direct : direct, (void*)d); else if (!tor_addr_is_null(&d->ipv6_addr)) @@ -1965,7 +1999,10 @@ router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, int need_uptime, int need_capacity, int need_guard, int need_desc, int pref_addr) -{ /* XXXX MOVE */ +{ + const int check_reach = !router_skip_or_reachability(get_options(), + pref_addr); + /* XXXX MOVE */ SMARTLIST_FOREACH_BEGIN(nodelist_get_list(), const node_t *, node) { if (!node->is_running || (!node->is_valid && !allow_invalid)) @@ -1977,7 +2014,9 @@ router_add_running_nodes_to_smartlist(smartlist_t *sl, int allow_invalid, if (node_is_unreliable(node, need_uptime, need_capacity, need_guard)) continue; /* Choose a node with an OR address that matches the firewall rules */ - if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, pref_addr)) + if (check_reach && !fascist_firewall_allows_node(node, + FIREWALL_OR_CONNECTION, + pref_addr)) continue; smartlist_add(sl, (void *)node); From 4db5a35e669a03db33d04632349ec95022de53cf Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Wed, 20 Jan 2016 13:17:08 +1100 Subject: [PATCH 11/15] Consistently format addresses in node_get_address_string Also, don't write to a buffer with length zero. --- src/or/nodelist.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/or/nodelist.c b/src/or/nodelist.c index c8f93bf30c..28d874133c 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -863,13 +863,13 @@ node_get_prim_addr_ipv4h(const node_t *node) void node_get_address_string(const node_t *node, char *buf, size_t len) { - if (node->ri) { - strlcpy(buf, fmt_addr32(node->ri->addr), len); - } else if (node->rs) { + uint32_t ipv4_addr = node_get_prim_addr_ipv4h(node); + + if (tor_addr_is_valid_ipv4h(ipv4_addr, 0)) { tor_addr_t addr; - tor_addr_from_ipv4h(&addr, node->rs->addr); + tor_addr_from_ipv4h(&addr, ipv4_addr); tor_addr_to_str(buf, &addr, len, 0); - } else { + } else if (len > 0) { buf[0] = '\0'; } } From 3a00215c35b01909a2db24132ab800298d61b647 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Thu, 21 Jan 2016 12:57:28 +1100 Subject: [PATCH 12/15] Minor whitespace-only fix --- src/or/connection.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index 0420f2656b..63bfb2e01e 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -1732,8 +1732,7 @@ connection_connect_log_client_use_ip_version(const connection_t *conn) /* Only clients care about ClientUseIPv4/6, bail out early on servers, and * on connections we don't care about */ - if (server_mode(options) || !conn - || conn->type == CONN_TYPE_EXIT) { + if (server_mode(options) || !conn || conn->type == CONN_TYPE_EXIT) { return; } From 77a9de0d48e61e6762e65f6099c9a424544eb0ad Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 22 Jan 2016 15:10:18 +1100 Subject: [PATCH 13/15] Automatically use IPv6 when ClientUseIPv4 is 0 Consequential changes to log messages: * it's no longer possible to disable both IPv4 and IPv6, * refactor common string out of remaining log messages --- src/or/config.c | 16 ++++++++-------- src/or/policies.c | 6 ++++-- src/test/test_entrynodes.c | 11 +++++++---- src/test/test_policy.c | 16 +++++++++------- 4 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/or/config.c b/src/or/config.c index caa01d1d93..b9d9fb2d9a 100644 --- a/src/or/config.c +++ b/src/or/config.c @@ -3108,20 +3108,20 @@ options_validate(or_options_t *old_options, or_options_t *options, /* We check if Reachable*Addresses blocks all addresses in * parse_reachable_addresses(). */ - if (options->ClientUseIPv4 == 0 && !fascist_firewall_use_ipv6(options)) - REJECT("Tor cannot connect to the Internet if ClientUseIPv4 is 0 and " - "ClientUseIPv6 is 0. Please set at least one of these options " - "to 1, or configure bridges."); + +#define WARN_PLEASE_USE_IPV6_LOG_MSG \ + "ClientPreferIPv6%sPort 1 is ignored unless tor is using IPv6. " \ + "Please set ClientUseIPv6 1, ClientUseIPv4 0, or configure bridges." if (!fascist_firewall_use_ipv6(options) && options->ClientPreferIPv6ORPort == 1) - log_warn(LD_CONFIG, "ClientPreferIPv6ORPort 1 is ignored unless " - "ClientUseIPv6 is also 1, or bridges are configured."); + log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "OR"); if (!fascist_firewall_use_ipv6(options) && options->ClientPreferIPv6DirPort == 1) - log_warn(LD_CONFIG, "ClientPreferIPv6DirPort 1 is ignored unless " - "ClientUseIPv6 is also 1, or bridges are configured."); + log_warn(LD_CONFIG, WARN_PLEASE_USE_IPV6_LOG_MSG, "Dir"); + +#undef WARN_PLEASE_USE_IPV6_LOG_MSG if (options->UseBridges && server_mode(options)) diff --git a/src/or/policies.c b/src/or/policies.c index 0dc4f96c8b..734558d836 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -420,11 +420,13 @@ fascist_firewall_allows_address(const tor_addr_t *addr, } /** Is this client configured to use IPv6? - * Clients use IPv6 if ClientUseIPv6 is 1, or UseBridges is 1. */ int fascist_firewall_use_ipv6(const or_options_t *options) { - return (options->ClientUseIPv6 == 1 || options->UseBridges == 1); + /* Clients use IPv6 if it's set, or they use bridges, or they don't use + * IPv4 */ + return (options->ClientUseIPv6 == 1 || options->UseBridges == 1 + || options->ClientUseIPv4 == 0); } /** Do we prefer to connect to IPv6, ignoring ClientPreferIPv6ORPort and diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index a0208b9cfc..14baa8c9bf 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -215,20 +215,23 @@ test_choose_random_entry_one_possible_guard(void *arg) * time, so we can't be sure we get the guard */ tt_assert(chosen_entry); - /* Check that we get the guard if it passes preferred address settings when - * they're auto */ + /* Check that we get a node if it is allowed but not preferred when settings + * are auto */ memset(&mocked_options, 0, sizeof(mocked_options)); mocked_options.ClientUseIPv4 = 1; mocked_options.ClientPreferIPv6ORPort = -1; chosen_entry = choose_random_entry(NULL); - tt_ptr_op(chosen_entry, OP_EQ, the_guard); + + /* We disable the guard check and the preferred address check at the same + * time, so we can't be sure we get the guard */ + tt_assert(chosen_entry); /* and with IPv6 active */ mocked_options.ClientUseIPv6 = 1; chosen_entry = choose_random_entry(NULL); - tt_ptr_op(chosen_entry, OP_EQ, the_guard); + tt_assert(chosen_entry); done: memset(&mocked_options, 0, sizeof(mocked_options)); diff --git a/src/test/test_policy.c b/src/test/test_policy.c index 1daa38ecf2..2e87f13fc0 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1310,7 +1310,8 @@ test_policies_fascist_firewall_allows_address(void *arg) tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) == 0); - /* Test the function's address matching with everything off */ + /* Test the function's address matching with ClientUseIPv4 0. + * This means "use IPv6" regardless of the other settings. */ memset(&mock_options, 0, sizeof(or_options_t)); mock_options.ClientUseIPv4 = 0; mock_options.ClientUseIPv6 = 0; @@ -1319,7 +1320,7 @@ test_policies_fascist_firewall_allows_address(void *arg) tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0) == 0); tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0) - == 0); + == 1); tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0) == 0); tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0) @@ -1596,7 +1597,8 @@ test_policies_fascist_firewall_choose_address(void *arg) FIREWALL_DIR_CONNECTION, 1) == &ipv6_dir_ap); - /* Choose an address with everything off */ + /* Choose an address with ClientUseIPv4 0. + * This means "use IPv6" regardless of the other settings. */ memset(&mock_options, 0, sizeof(or_options_t)); mock_options.ClientUseIPv4 = 0; mock_options.ClientUseIPv6 = 0; @@ -1604,16 +1606,16 @@ test_policies_fascist_firewall_choose_address(void *arg) tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, FIREWALL_OR_CONNECTION, 0) - == NULL); + == &ipv6_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, FIREWALL_OR_CONNECTION, 1) - == NULL); + == &ipv6_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 0) - == NULL); + == &ipv6_dir_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 1) - == NULL); + == &ipv6_dir_ap); /* Choose from unusual inputs */ memset(&mock_options, 0, sizeof(or_options_t)); From 1401117ff2bc5fc90df51d19c3c0d7abc439c34e Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 22 Jan 2016 17:43:24 +1100 Subject: [PATCH 14/15] Return NULL from extend_info_from_node if the node has no allowed address Modify callers to correctly handle these new NULL returns: * fix assert in onion_extend_cpath * warn and discard circuit in circuit_get_open_circ_or_launch * warn, discard circuit, and tell controller in handle_control_extendcircuit --- src/or/circuitbuild.c | 35 +++++++++++++++++++++++------------ src/or/circuituse.c | 7 ++++++- src/or/control.c | 18 ++++++++++++++++-- 3 files changed, 45 insertions(+), 15 deletions(-) diff --git a/src/or/circuitbuild.c b/src/or/circuitbuild.c index dcb9de3a80..daf0b2af0b 100644 --- a/src/or/circuitbuild.c +++ b/src/or/circuitbuild.c @@ -2241,9 +2241,11 @@ onion_extend_cpath(origin_circuit_t *circ) if (r) { /* 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); + port. Servers always want the primary (IPv4) address. */ + int client = (server_mode(get_options()) == 0); + info = extend_info_from_node(r, client); + /* Clients can fail to find an allowed address */ + tor_assert(info || client); } } else { const node_t *r = @@ -2318,34 +2320,43 @@ extend_info_new(const char *nickname, const char *digest, * 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. + * routerinfo_t or microdesc_t, or if for_direct_connect is true and none of + * the node's addresses are allowed by tor's firewall and IP version config. **/ extend_info_t * extend_info_from_node(const node_t *node, int for_direct_connect) { tor_addr_port_t ap; + int valid_addr = 0; if (node->ri == NULL && (node->rs == NULL || node->md == NULL)) return NULL; - /* Choose a preferred address first, but fall back to an allowed address*/ + /* Choose a preferred address first, but fall back to an allowed address. + * choose_address returns 1 on success, but get_prim_orport returns 0. */ if (for_direct_connect) - fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, &ap); + valid_addr = fascist_firewall_choose_address_node(node, + FIREWALL_OR_CONNECTION, + 0, &ap); else - node_get_prim_orport(node, &ap); + valid_addr = !node_get_prim_orport(node, &ap); - log_debug(LD_CIRC, "using %s for %s", - fmt_addrport(&ap.addr, ap.port), - node->ri ? node->ri->nickname : node->rs->nickname); + if (valid_addr) + log_debug(LD_CIRC, "using %s for %s", + fmt_addrport(&ap.addr, ap.port), + node->ri ? node->ri->nickname : node->rs->nickname); + else + log_warn(LD_CIRC, "Could not choose valid address for %s", + node->ri ? node->ri->nickname : node->rs->nickname); - if (node->ri) + if (valid_addr && node->ri) return extend_info_new(node->ri->nickname, node->identity, node->ri->onion_pkey, node->ri->onion_curve25519_pkey, &ap.addr, ap.port); - else if (node->rs && node->md) + else if (valid_addr && node->rs && node->md) return extend_info_new(node->rs->nickname, node->identity, node->md->onion_pkey, diff --git a/src/or/circuituse.c b/src/or/circuituse.c index e742a5614f..4831f2be76 100644 --- a/src/or/circuituse.c +++ b/src/or/circuituse.c @@ -2006,8 +2006,13 @@ circuit_get_open_circ_or_launch(entry_connection_t *conn, if (r && node_has_descriptor(r)) { /* We might want to connect to an IPv6 bridge for loading descriptors so we use the preferred address rather than - the primary. */ + the primary. */ extend_info = extend_info_from_node(r, conn->want_onehop ? 1 : 0); + if (!extend_info) { + log_warn(LD_CIRC,"Could not make a one-hop connection to %s. " + "Discarding this circuit.", conn->chosen_exit_name); + return -1; + } } else { log_debug(LD_DIR, "considering %d, %s", want_onehop, conn->chosen_exit_name); diff --git a/src/or/control.c b/src/or/control.c index 66182fe2a4..2c0209ed85 100644 --- a/src/or/control.c +++ b/src/or/control.c @@ -2864,12 +2864,26 @@ handle_control_extendcircuit(control_connection_t *conn, uint32_t len, } /* now circ refers to something that is ready to be extended */ + int first_node = zero_circ; SMARTLIST_FOREACH(nodes, const node_t *, node, { - extend_info_t *info = extend_info_from_node(node, 0); - tor_assert(info); /* True, since node_has_descriptor(node) == true */ + extend_info_t *info = extend_info_from_node(node, first_node); + if (first_node && !info) { + log_warn(LD_CONTROL, + "controller tried to connect to a node that doesn't have any " + "addresses that are allowed by the firewall configuration; " + "circuit marked for closing."); + circuit_mark_for_close(TO_CIRCUIT(circ), -END_CIRC_REASON_CONNECTFAILED); + connection_write_str_to_buf("551 Couldn't start circuit\r\n", conn); + goto done; + } else { + /* True, since node_has_descriptor(node) == true and we are extending + * to the node's primary address */ + tor_assert(info); + } circuit_append_new_exit(circ, info); extend_info_free(info); + first_node = 0; }); /* now that we've populated the cpath, start extending */ From 73fc67bc8906819a42ed44abe33179512f90a883 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 22 Jan 2016 18:05:28 +1100 Subject: [PATCH 15/15] Tor2Web: tell extend_info_from_node intro point connections are direct --- src/or/rendclient.c | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/or/rendclient.c b/src/or/rendclient.c index d9cea53c04..dc05d6f2ed 100644 --- a/src/or/rendclient.c +++ b/src/or/rendclient.c @@ -1366,11 +1366,19 @@ rend_client_get_random_intro_impl(const rend_cache_entry_t *entry, smartlist_del(usable_nodes, i); goto again; } +#ifdef ENABLE_TOR2WEB_MODE + new_extend_info = extend_info_from_node(node, options->Tor2webMode); +#else new_extend_info = extend_info_from_node(node, 0); +#endif if (!new_extend_info) { + const char *alternate_reason = ""; +#ifdef ENABLE_TOR2WEB_MODE + alternate_reason = ", or we cannot connect directly to it"; +#endif log_info(LD_REND, "We don't have a descriptor for the intro-point relay " - "'%s'; trying another.", - extend_info_describe(intro->extend_info)); + "'%s'%s; trying another.", + extend_info_describe(intro->extend_info), alternate_reason); smartlist_del(usable_nodes, i); goto again; } else {