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] 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 };