From 16486662038de53c482cd6f50a30505f2bf20453 Mon Sep 17 00:00:00 2001 From: "teor (Tim Wilson-Brown)" Date: Sun, 3 Jan 2016 18:20:37 +1100 Subject: Choose bridge addresses by IPv4/IPv6 preferences --- src/or/directory.c | 176 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 107 insertions(+), 69 deletions(-) (limited to 'src/or/directory.c') diff --git a/src/or/directory.c b/src/or/directory.c index 20ffcee8dd..665ba27767 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -497,11 +497,14 @@ MOCK_IMPL(void, directory_get_from_dirserver, ( const node_t *node = choose_random_dirguard(type); if (node && node->ri) { /* every bridge has a routerinfo. */ - tor_addr_t addr; routerinfo_t *ri = node->ri; - node_get_addr(node, &addr); - directory_initiate_command(&addr, - ri->or_port, 0/*no dirport*/, + /* clients always make OR connections to bridges */ + tor_addr_port_t or_ap; + /* we are willing to use a non-preferred address if we need to */ + fascist_firewall_choose_address_node(node, FIREWALL_OR_CONNECTION, 0, + &or_ap); + directory_initiate_command(&or_ap.addr, or_ap.port, + NULL, 0, /*no dirport*/ ri->cache_info.identity_digest, dir_purpose, router_purpose, @@ -610,6 +613,80 @@ dirind_is_anon(dir_indirection_t ind) return ind == DIRIND_ANON_DIRPORT || ind == DIRIND_ANONYMOUS; } +/* Choose reachable OR and Dir addresses and ports from status, copying them + * into use_or_ap and use_dir_ap. If indirection is anonymous, then we're + * connecting via another relay, so choose the primary IPv4 address and ports. + * + * status should have at least one reachable address, if we can't choose a + * reachable address, warn and return -1. Otherwise, return 0. + */ +static int +directory_choose_address_routerstatus(const routerstatus_t *status, + dir_indirection_t indirection, + tor_addr_port_t *use_or_ap, + tor_addr_port_t *use_dir_ap) +{ + tor_assert(status != NULL); + tor_assert(use_or_ap != NULL); + tor_assert(use_dir_ap != NULL); + + const int anonymized_connection = dirind_is_anon(indirection); + int have_or = 0, have_dir = 0; + + /* We expect status to have at least one reachable address if we're + * connecting to it directly. + * + * Therefore, we can simply use the other address if the one we want isn't + * allowed by the firewall. + * + * (When Tor uploads and downloads a hidden service descriptor, it uses + * DIRIND_ANONYMOUS, except for Tor2Web, which uses DIRIND_ONEHOP. + * So this code will only modify the address for Tor2Web's HS descriptor + * fetches. Even Single Onion Servers (NYI) use DIRIND_ANONYMOUS, to avoid + * HSDirs denying service by rejecting descriptors.) + */ + + /* Initialise the OR / Dir addresses */ + tor_addr_make_null(&use_or_ap->addr, AF_UNSPEC); + use_or_ap->port = 0; + tor_addr_make_null(&use_dir_ap->addr, AF_UNSPEC); + use_dir_ap->port = 0; + + if (anonymized_connection) { + /* Use the primary (IPv4) OR address if we're making an indirect + * connection. */ + tor_addr_from_ipv4h(&use_or_ap->addr, status->addr); + use_or_ap->port = status->or_port; + have_or = 1; + } else { + /* We use an IPv6 address if we have one and we prefer it. + * Use the preferred address and port if they are reachable, otherwise, + * use the alternate address and port (if any). + */ + have_or = fascist_firewall_choose_address_rs(status, + FIREWALL_OR_CONNECTION, 0, + use_or_ap); + } + + have_dir = fascist_firewall_choose_address_rs(status, + FIREWALL_DIR_CONNECTION, 0, + use_dir_ap); + + /* We rejected both addresses. This isn't great. */ + if (!have_or && !have_dir) { + log_warn(LD_BUG, "Rejected all OR and Dir addresses from %s when " + "launching a directory connection to: IPv4 %s OR %d Dir %d " + "IPv6 %s OR %d Dir %d", routerstatus_describe(status), + fmt_addr32(status->addr), status->or_port, + status->dir_port, fmt_addr(&status->ipv6_addr), + status->ipv6_orport, status->dir_port); + log_backtrace(LOG_WARN, LD_BUG, "Addresses came from"); + return -1; + } + + return 0; +} + /** Same as directory_initiate_command_routerstatus(), but accepts * rendezvous data to fetch a hidden service descriptor. */ void @@ -627,7 +704,8 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, const node_t *node; tor_addr_port_t use_or_ap, use_dir_ap; const int anonymized_connection = dirind_is_anon(indirection); - int have_or = 0, have_dir = 0; + + tor_assert(status != NULL); node = node_get_by_id(status->identity_digest); @@ -654,57 +732,16 @@ directory_initiate_command_routerstatus_rend(const routerstatus_t *status, * selection uses the preference in ClientPreferIPv6{OR,Dir}Port, if * possible. (If UseBridges is set, clients ignore all these settings.) * - * Now we use a similar process to select an address for the relay, - * but simply use the other address if the one we want isn't allowed by - * the firewall. - * - * (When Tor uploads and downloads a hidden service descriptor, it uses - * DIRIND_ANONYMOUS, except for Tor2Web, which uses DIRIND_ONEHOP. - * So this code will only modify the address for Tor2Web's HS descriptor - * fetches. Even Single Onion Servers (NYI) use DIRIND_ANONYMOUS, to avoid - * HSDirs denying service by rejecting descriptors.) + * Now choose an address that we can use to connect to the directory server. */ - - /* Initialise the OR / Dir addresses */ - tor_addr_make_null(&use_or_ap.addr, AF_UNSPEC); - use_or_ap.port = 0; - tor_addr_make_null(&use_dir_ap.addr, AF_UNSPEC); - use_dir_ap.port = 0; - - if (anonymized_connection) { - /* Use the primary (IPv4) OR address if we're making an indirect - * connection. */ - tor_addr_from_ipv4h(&use_or_ap.addr, status->addr); - use_or_ap.port = status->or_port; - have_or = 1; - } else { - /* We use an IPv6 address if we have one and we prefer it. - * Use the preferred address and port if they are reachable, otherwise, - * use the alternate address and port (if any). - */ - have_or = fascist_firewall_choose_address_rs(status, - FIREWALL_OR_CONNECTION, 0, - &use_or_ap); - } - - have_dir = fascist_firewall_choose_address_rs(status, - FIREWALL_DIR_CONNECTION, 0, - &use_dir_ap); - - /* We rejected both addresses. This isn't great. */ - if (!have_or && !have_dir) { - log_warn(LD_BUG, "Rejected all OR and Dir addresses from %s when " - "launching a directory connection to: IPv4 %s OR %d Dir %d " - "IPv6 %s OR %d Dir %d", routerstatus_describe(status), - fmt_addr32(status->addr), status->or_port, - status->dir_port, fmt_addr(&status->ipv6_addr), - status->ipv6_orport, status->dir_port); - log_backtrace(LOG_WARN, LD_BUG, "Addresses came from"); + if (directory_choose_address_routerstatus(status, indirection, &use_or_ap, + &use_dir_ap) < 0) { return; } - /* XX/teor - we don't retry the alternate OR/Dir address if this one fails. - * (See #6772.) Instead, we'll retry another directory on failure. */ + /* We don't retry the alternate OR/Dir address for the same directory if + * the address we choose fails (#6772). + * Instead, we'll retry another directory on failure. */ directory_initiate_command_rend(&use_or_ap, &use_dir_ap, status->identity_digest, @@ -941,41 +978,42 @@ directory_command_should_use_begindir(const or_options_t *options, } /** Helper for directory_initiate_command_rend: send the - * command to a server whose address is _addr, whose OR port is - * or_port, whose directory port is dir_port, whose identity key - * digest is digest, with purposes dir_purpose and + * command to a server whose OR address/port is or_addr/or_port, + * whose directory address/port is dir_addr/dir_port, whose + * identity key digest is digest, with purposes dir_purpose and * router_purpose, making an (in)direct connection as specified in * indirection, with command resource, payload of * payload_len, and asking for a result only if_modified_since. */ void -directory_initiate_command(const tor_addr_t *_addr, - uint16_t or_port, uint16_t dir_port, +directory_initiate_command(const tor_addr_t *or_addr, uint16_t or_port, + const tor_addr_t *dir_addr, uint16_t dir_port, const char *digest, uint8_t dir_purpose, uint8_t router_purpose, dir_indirection_t indirection, const char *resource, const char *payload, size_t payload_len, time_t if_modified_since) { - /* Assume _addr applies to both the ORPort and the DirPort, but use the - * null tor_addr if ORPort or DirPort are 0. */ tor_addr_port_t or_ap, dir_ap; - if (or_port) { - tor_addr_copy(&or_ap.addr, _addr); + /* Use the null tor_addr and 0 port if the address or port isn't valid. */ + if (tor_addr_port_is_valid(or_addr, or_port, 0)) { + tor_addr_copy(&or_ap.addr, or_addr); + or_ap.port = or_port; } else { - /* the family doesn't matter here, so make it the same as _addr */ - tor_addr_make_null(&or_ap.addr, tor_addr_family(_addr)); + /* the family doesn't matter here, so make it IPv4 */ + tor_addr_make_null(&or_ap.addr, AF_INET); + or_port = 0; } - or_ap.port = or_port; - if (dir_port) { - tor_addr_copy(&dir_ap.addr, _addr); + if (tor_addr_port_is_valid(dir_addr, dir_port, 0)) { + tor_addr_copy(&dir_ap.addr, dir_addr); + dir_ap.port = dir_port; } else { - /* the family doesn't matter here, so make it the same as _addr */ - tor_addr_make_null(&dir_ap.addr, tor_addr_family(_addr)); + /* the family doesn't matter here, so make it IPv4 */ + tor_addr_make_null(&dir_ap.addr, AF_INET); + dir_port = 0; } - dir_ap.port = dir_port; directory_initiate_command_rend(&or_ap, &dir_ap, digest, dir_purpose, -- cgit v1.2.3-54-g00ecf