From 2d33d192fc4dd0da2a2e038dd87b277f8e9b90de Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Mon, 14 Dec 2015 17:23:10 +1100 Subject: 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. --- src/or/policies.c | 735 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 700 insertions(+), 35 deletions(-) (limited to 'src/or/policies.c') 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 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_address_addr(const tor_addr_t *addr, uint16_t port, + firewall_connection_t fw_connection, + int pref_only) +{ + const or_options_t *options = get_options(); + + 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 { + 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 an OR connection to - * ri. */ +/** 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_or(const routerinfo_t *ri) +fascist_firewall_allows_address_ap(const tor_addr_port_t *ap, + 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); + 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 an OR connection to - * node. */ +/* 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_node(const node_t *node) +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 (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 (!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 directory - * connection to addr:port. */ +/** 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_address_dir(const tor_addr_t *addr, uint16_t port) +fascist_firewall_allows_md(const microdesc_t *md, + firewall_connection_t fw_connection, + int pref_only) { - return addr_policy_permits_tor_addr(addr, port, - reachable_dir_addr_policy); + 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, -- cgit v1.2.3-54-g00ecf From 3b8216f2155f224bf66497c71de4cecb55cd83e6 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Mon, 4 Jan 2016 00:35:22 +1100 Subject: 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 | 154 +++++++++++++++++------ 12 files changed, 403 insertions(+), 385 deletions(-) (limited to 'src/or/policies.c') 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++) { - - /* 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); - } - } - } - } - } - } + /* 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); + + /* 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)); -- cgit v1.2.3-54-g00ecf From e991d642ec14d41df9da70442d99861bdb5bfb5b Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Thu, 21 Jan 2016 12:58:59 +1100 Subject: 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(-) (limited to 'src/or/policies.c') 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); -- cgit v1.2.3-54-g00ecf From 772577b547aa5e6b13b89f465acf656cd08f2917 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Thu, 21 Jan 2016 13:30:57 +1100 Subject: 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(-) (limited to 'src/or/policies.c') 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); -- cgit v1.2.3-54-g00ecf From 77a9de0d48e61e6762e65f6099c9a424544eb0ad Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Fri, 22 Jan 2016 15:10:18 +1100 Subject: 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(-) (limited to 'src/or/policies.c') 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)); -- cgit v1.2.3-54-g00ecf From c213f277cde00b258b159446f8d975026194c034 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Wed, 3 Feb 2016 23:52:39 +1100 Subject: Make bridge clients prefer the configured bridge address When ClientPreferIPv6ORPort is auto, bridges prefer the configured bridge ORPort address. Otherwise, they use the value of the option. Other clients prefer IPv4 ORPorts if ClientPreferIPv6ORPort is auto. When ClientPreferIPv6DirPort is auto, all clients prefer IPv4 DirPorts. --- doc/tor.1.txt | 12 ++++++------ src/or/entrynodes.c | 22 ++++++++++++++++------ src/or/nodelist.c | 24 +++++++++++------------- src/or/policies.c | 18 +++++++----------- src/test/test_entrynodes.c | 10 ++++++---- src/test/test_policy.c | 25 +++++++++++++++---------- 6 files changed, 61 insertions(+), 50 deletions(-) (limited to 'src/or/policies.c') diff --git a/doc/tor.1.txt b/doc/tor.1.txt index 87d976b2fa..4c9c53d8cb 100644 --- a/doc/tor.1.txt +++ b/doc/tor.1.txt @@ -1504,17 +1504,17 @@ The following options are useful only for clients (that is, if 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.) 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) + 0.) If this option is set to auto, 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**|**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.) 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) + to auto, Tor bridge clients prefer the configured bridge address, 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/entrynodes.c b/src/or/entrynodes.c index d6bef658ff..a4b935065d 100644 --- a/src/or/entrynodes.c +++ b/src/or/entrynodes.c @@ -2240,6 +2240,7 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) * does so through an address from any source other than node_get_addr(). */ tor_addr_t addr; + const or_options_t *options = get_options(); if (node->ri) { routerinfo_t *ri = node->ri; @@ -2272,9 +2273,15 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) } } - /* Mark which address to use based on which bridge_t we got. */ - node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && - !tor_addr_is_null(&node->ri->ipv6_addr)); + if (options->ClientPreferIPv6ORPort == -1) { + /* Mark which address to use based on which bridge_t we got. */ + node->ipv6_preferred = (tor_addr_family(&bridge->addr) == AF_INET6 && + !tor_addr_is_null(&node->ri->ipv6_addr)); + } else { + /* Mark which address to use based on user preference */ + node->ipv6_preferred = (fascist_firewall_prefer_ipv6_orport(options) && + !tor_addr_is_null(&node->ri->ipv6_addr)); + } /* XXXipv6 we lack support for falling back to another address for the same relay, warn the user */ @@ -2283,10 +2290,13 @@ rewrite_node_address_for_bridge(const bridge_info_t *bridge, node_t *node) node_get_pref_orport(node, &ap); log_notice(LD_CONFIG, "Bridge '%s' has both an IPv4 and an IPv6 address. " - "Will prefer using its %s address (%s).", + "Will prefer using its %s address (%s) based on %s.", ri->nickname, - tor_addr_family(&ap.addr) == AF_INET6 ? "IPv6" : "IPv4", - fmt_addrport(&ap.addr, ap.port)); + node->ipv6_preferred ? "IPv6" : "IPv4", + fmt_addrport(&ap.addr, ap.port), + options->ClientPreferIPv6ORPort == -1 ? + "the configured Bridge address" : + "ClientPreferIPv6ORPort"); } } if (node->rs) { diff --git a/src/or/nodelist.c b/src/or/nodelist.c index d7cada94d3..23e9b0e176 100644 --- a/src/or/nodelist.c +++ b/src/or/nodelist.c @@ -981,10 +981,6 @@ node_has_ipv6_dirport(const node_t *node) * i) the node_t says that it prefers IPv6 * or * 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_or_preferred(const node_t *node) @@ -993,10 +989,12 @@ node_ipv6_or_preferred(const node_t *node) tor_addr_port_t ipv4_addr; node_assert_ok(node); + /* XX/teor - node->ipv6_preferred is set from + * fascist_firewall_prefer_ipv6_orport() each time the consensus is loaded. + */ if (!fascist_firewall_use_ipv6(options)) { return 0; - } else if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr) - || fascist_firewall_prefer_ipv6_orport(get_options())) { + } else if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr)) { return node_has_ipv6_orport(node); } return 0; @@ -1077,13 +1075,9 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out) * * 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 + * i) the router has no IPv4 Dir address. * 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.) + * ii) our preference is for IPv6 Dir addresses. */ int node_ipv6_dir_preferred(const node_t *node) @@ -1092,9 +1086,13 @@ node_ipv6_dir_preferred(const node_t *node) tor_addr_port_t ipv4_addr; node_assert_ok(node); + /* node->ipv6_preferred is set from fascist_firewall_prefer_ipv6_orport(), + * so we can't use it to determine DirPort IPv6 preference. + * This means that bridge clients will use IPv4 DirPorts by default. + */ if (!fascist_firewall_use_ipv6(options)) { return 0; - } else if (node->ipv6_preferred || node_get_prim_dirport(node, &ipv4_addr) + } else if (node_get_prim_dirport(node, &ipv4_addr) || fascist_firewall_prefer_ipv6_dirport(get_options())) { return node_has_ipv6_dirport(node); } diff --git a/src/or/policies.c b/src/or/policies.c index 734558d836..e2cece56e4 100644 --- a/src/or/policies.c +++ b/src/or/policies.c @@ -458,6 +458,13 @@ fascist_firewall_prefer_ipv6_impl(const or_options_t *options) int fascist_firewall_prefer_ipv6_orport(const or_options_t *options) { + /* node->ipv6_preferred is set from fascist_firewall_prefer_ipv6_orport() + * each time the consensus is loaded. + * If our preferences change, we will only reset ipv6_preferred on the node + * when the next consensus is loaded. But the consensus is realoded when the + * configuration changes after a HUP. So as long as the result of this + * function only depends on Tor's options, everything should work ok. + */ int pref_ipv6 = fascist_firewall_prefer_ipv6_impl(options); if (pref_ipv6 >= 0) { @@ -469,11 +476,6 @@ fascist_firewall_prefer_ipv6_orport(const or_options_t *options) return 1; } - /* For bridge clients, ClientPreferIPv6ORPort auto means "prefer IPv6". */ - if (options->UseBridges && options->ClientPreferIPv6ORPort != 0) { - return 1; - } - return 0; } @@ -493,12 +495,6 @@ fascist_firewall_prefer_ipv6_dirport(const or_options_t *options) 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; } diff --git a/src/test/test_entrynodes.c b/src/test/test_entrynodes.c index 14baa8c9bf..fd19db095d 100644 --- a/src/test/test_entrynodes.c +++ b/src/test/test_entrynodes.c @@ -9,14 +9,16 @@ #include "or.h" #include "test.h" + +#include "config.h" #include "entrynodes.h" -#include "routerparse.h" #include "nodelist.h" -#include "util.h" +#include "policies.h" #include "routerlist.h" +#include "routerparse.h" #include "routerset.h" #include "statefile.h" -#include "config.h" +#include "util.h" #include "test_helpers.h" @@ -826,7 +828,7 @@ test_node_preferred_orport(void *arg) * ClientUseIPv4 is 0 */ mocked_options.ClientUseIPv4 = 0; mocked_options.ClientUseIPv6 = 1; - node.ipv6_preferred = 0; + node.ipv6_preferred = fascist_firewall_prefer_ipv6_orport(&mocked_options); node_get_pref_orport(&node, &ap); tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr)); tt_assert(ap.port == ipv6_port); diff --git a/src/test/test_policy.c b/src/test/test_policy.c index b4cbfb2579..c044d9f210 100644 --- a/src/test/test_policy.c +++ b/src/test/test_policy.c @@ -1510,21 +1510,25 @@ test_policies_fascist_firewall_choose_address(void *arg) FIREWALL_DIR_CONNECTION, 1) == &ipv4_dir_ap); - /* Auto (Preferring IPv6 for bridge clients) */ + /* Auto: + * - bridge clients prefer the configured bridge OR address, + * - other clients prefer IPv4 OR by default, + * - all clients prefer IPv4 Dir by default. + */ 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); + == &ipv4_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, FIREWALL_OR_CONNECTION, 1) - == &ipv6_or_ap); + == &ipv4_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 0) - == &ipv6_dir_ap); + == &ipv4_dir_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 1) - == &ipv6_dir_ap); + == &ipv4_dir_ap); /* Preferring IPv6 */ mock_options.ClientPreferIPv6ORPort = 1; @@ -1544,22 +1548,23 @@ test_policies_fascist_firewall_choose_address(void *arg) /* In the default configuration (Auto / IPv6 off), bridge clients should - * still use and prefer IPv6 regardless of ClientUseIPv6. */ + * still use IPv6, and only prefer it for bridges configured with an IPv6 + * address, 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); + == &ipv4_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0, FIREWALL_OR_CONNECTION, 1) - == &ipv6_or_ap); + == &ipv4_or_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 0) - == &ipv6_dir_ap); + == &ipv4_dir_ap); tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0, FIREWALL_DIR_CONNECTION, 1) - == &ipv6_dir_ap); + == &ipv4_dir_ap); /* Choose an address with IPv4 on */ memset(&mock_options, 0, sizeof(or_options_t)); -- cgit v1.2.3-54-g00ecf