Browse Source

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.
teor (Tim Wilson-Brown) 8 years ago
parent
commit
3b8216f215
12 changed files with 403 additions and 385 deletions
  1. 9 7
      doc/tor.1.txt
  2. 12 37
      src/or/config.c
  3. 11 9
      src/or/connection.c
  4. 11 1
      src/or/directory.c
  5. 56 100
      src/or/nodelist.c
  6. 3 2
      src/or/nodelist.h
  7. 7 3
      src/or/or.h
  8. 124 172
      src/or/policies.c
  9. 5 12
      src/or/policies.h
  10. 4 4
      src/or/routerlist.c
  11. 43 2
      src/test/test_entrynodes.c
  12. 118 36
      src/test/test_policy.c

+ 9 - 7
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

+ 12 - 37
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))

+ 11 - 9
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);
   }
 }
 

+ 11 - 1
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

+ 56 - 100
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 <b>ns</b>.
  * 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
  * <b>node</b>, 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 {

+ 3 - 2
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);

+ 7 - 3
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;
 

+ 124 - 172
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 <b>policy</b> (possibly NULL) will allow a
  * connection to <b>addr</b>:<b>port</b>.
  */
@@ -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
- * <b>ri</b> on either its IPv4 or IPv6 address. Uses
- * or_port/ipv6_orport/ReachableORAddresses or dir_port/ReachableDirAddresses
- * based on IPv4/IPv6 and <b>fw_connection</b>.
- * 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
- * <b>md</b> on its IPv6 address. (The IPv4 address is in the routerstatus and
- * the routerinfo.) Uses ipv6_orport/ReachableORAddresses or
- * dir_port/ReachableDirAddresses based on <b>fw_connection</b>.
- * 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
  * <b>node</b>:
  *  - if <b>preferred</b> 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 <b>ri</b> into <b>ap</b> 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
- * <b>fw_connection</b>.
- * 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 <b>rs</b> into <b>ap</b> 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 <b>md</b> into <b>ap</b> 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 <b>fw_connection</b> 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 <b>node</b> into <b>ap</b> that we think our
  * firewall will let us connect to. Uses ipv4h_addr/ipv6_addr and
  * ipv4_orport/ipv6_orport/ReachableORAddresses or

+ 5 - 12
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);

+ 4 - 4
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 <b>CRN_PREF_ADDR</b> 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,

+ 43 - 2
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 */

+ 118 - 36
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));