Browse Source

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.
teor (Tim Wilson-Brown) 8 years ago
parent
commit
2d33d192fc

+ 9 - 0
changes/feature17840

@@ -0,0 +1,9 @@
+  o Minor feature (IPv6):
+    - Add ClientUseIPv4, which is set to 1 by default. If set to 0, tor
+      avoids using IPv4 for client OR and directory connections.
+    - Add ClientPreferIPv6DirPort, which is set to 0 by default. If set
+      to 1, tor prefers IPv6 directory addresses.
+    - Try harder to fulfil IP version restrictions ClientUseIPv4 0 and
+      ClientUseIPv6 0; and the preferences ClientPreferIPv6ORPort and
+      ClientPreferIPv6DirPort.
+      Closes ticket 17840; patch by "teor".

+ 26 - 7
doc/tor.1.txt

@@ -367,6 +367,7 @@ GENERAL OPTIONS
     authorities.
     authorities.
     By default, the directory authorities are also FallbackDirs. Specifying a
     By default, the directory authorities are also FallbackDirs. Specifying a
     FallbackDir replaces Tor's default hard-coded FallbackDirs (if any).
     FallbackDir replaces Tor's default hard-coded FallbackDirs (if any).
+    (See the **DirAuthority** entry for an explanation of each flag.)
 
 
 [[UseDefaultFallbackDirs]] **UseDefaultFallbackDirs** **0**|**1**::
 [[UseDefaultFallbackDirs]] **UseDefaultFallbackDirs** **0**|**1**::
     Use Tor's default hard-coded FallbackDirs (if any). (When a
     Use Tor's default hard-coded FallbackDirs (if any). (When a
@@ -390,6 +391,10 @@ GENERAL OPTIONS
     if an "ipv6=__address__:__orport__" flag is present, then the directory
     if an "ipv6=__address__:__orport__" flag is present, then the directory
     authority is listening for IPv6 connections on the indicated IPv6 address
     authority is listening for IPv6 connections on the indicated IPv6 address
     and OR Port. +
     and OR Port. +
+ +
+    Tor will contact the authority at __address__:__port__ (the DirPort) to
+    download directory documents. If an IPv6 address is supplied, Tor will
+    also download directory documents at the IPv6 address on the DirPort. +
  +
  +
     If no **DirAuthority** line is given, Tor will use the default directory
     If no **DirAuthority** line is given, Tor will use the default directory
     authorities. NOTE: this option is intended for setting up a private Tor
     authorities. NOTE: this option is intended for setting up a private Tor
@@ -1483,17 +1488,31 @@ The following options are useful only for clients (that is, if
     If no defaults are available there, these options default to 20, .80,
     If no defaults are available there, these options default to 20, .80,
     .60, and 100, respectively.
     .60, and 100, respectively.
 
 
+[[ClientUseIPv4]] **ClientUseIPv4** **0**|**1**::
+    If this option is set to 0, Tor will avoid connecting to directory servers
+    and entry nodes over IPv4. Note that clients with an IPv4
+    address in a **Bridge**, proxy, or pluggable transport line will try
+    connecting over IPv4 even if **ClientUseIPv4** is set to 0. (Default: 1)
+
 [[ClientUseIPv6]] **ClientUseIPv6** **0**|**1**::
 [[ClientUseIPv6]] **ClientUseIPv6** **0**|**1**::
-    If this option is set to 1, Tor might connect to entry nodes over
-    IPv6. Note that clients configured with an IPv6 address in a
-    **Bridge** line will try connecting over IPv6 even if
-    **ClientUseIPv6** is set to 0. (Default: 0)
+    If this option is set to 1, Tor might connect to directory servers or
+    entry nodes over IPv6. Note that clients configured with an IPv6 address
+    in a **Bridge**, proxy, or pluggable transport line will try connecting
+    over IPv6 even if **ClientUseIPv6** is set to 0. (Default: 0)
+
+[[ClientPreferIPv6DirPort]] **ClientPreferIPv6DirPort** **0**|**1**::
+    If this option is set to 1, Tor prefers a directory port with an IPv6
+    address over one with IPv4, for direct connections, if a given directory
+    server has both. (Tor also prefers an IPv6 DirPort if IPv4Client is set to
+    0.) Other things may influence the choice. This option breaks a tie to the
+    favor of IPv6. (Default: 0)
 
 
 [[ClientPreferIPv6ORPort]] **ClientPreferIPv6ORPort** **0**|**1**::
 [[ClientPreferIPv6ORPort]] **ClientPreferIPv6ORPort** **0**|**1**::
     If this option is set to 1, Tor prefers an OR port with an IPv6
     If this option is set to 1, Tor prefers an OR port with an IPv6
-    address over one with IPv4 if a given entry node has both. Other
-    things may influence the choice. This option breaks a tie to the
-    favor of IPv6. (Default: 0)
+    address over one with IPv4 if a given entry node has both. (Tor also
+    prefers an IPv6 ORPort if IPv4Client is set to 0.) Other things may
+    influence the choice. This option breaks a tie to the favor of IPv6.
+    (Default: 0)
 
 
 [[PathsNeededToBuildCircuits]] **PathsNeededToBuildCircuits** __NUM__::
 [[PathsNeededToBuildCircuits]] **PathsNeededToBuildCircuits** __NUM__::
     Tor clients don't build circuits for user traffic until they know
     Tor clients don't build circuits for user traffic until they know

+ 6 - 8
src/or/circuitbuild.c

@@ -2161,14 +2161,12 @@ choose_good_entry_server(uint8_t purpose, cpath_build_state_t *state)
      * family. */
      * family. */
     nodelist_add_node_and_family(excluded, node);
     nodelist_add_node_and_family(excluded, node);
   }
   }
-  if (firewall_is_fascist_or()) {
-    /* Exclude all ORs that we can't reach through our firewall */
-    smartlist_t *nodes = nodelist_get_list();
-    SMARTLIST_FOREACH(nodes, const node_t *, node, {
-      if (!fascist_firewall_allows_node(node))
-        smartlist_add(excluded, (void*)node);
-    });
-  }
+  /* Exclude all ORs that we can't reach through our firewall */
+  smartlist_t *nodes = nodelist_get_list();
+  SMARTLIST_FOREACH(nodes, const node_t *, node, {
+    if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0))
+      smartlist_add(excluded, (void*)node);
+  });
   /* and exclude current entry guards and their families,
   /* and exclude current entry guards and their families,
    * unless we're in a test network, and excluding guards
    * unless we're in a test network, and excluding guards
    * would exclude all nodes (i.e. we're in an incredibly small tor network,
    * would exclude all nodes (i.e. we're in an incredibly small tor network,

+ 48 - 3
src/or/config.c

@@ -191,9 +191,11 @@ static config_var_t option_vars_[] = {
   V(ClientDNSRejectInternalAddresses, BOOL,"1"),
   V(ClientDNSRejectInternalAddresses, BOOL,"1"),
   V(ClientOnly,                  BOOL,     "0"),
   V(ClientOnly,                  BOOL,     "0"),
   V(ClientPreferIPv6ORPort,      BOOL,     "0"),
   V(ClientPreferIPv6ORPort,      BOOL,     "0"),
+  V(ClientPreferIPv6DirPort,     BOOL,     "0"),
   V(ClientRejectInternalAddresses, BOOL,   "1"),
   V(ClientRejectInternalAddresses, BOOL,   "1"),
   V(ClientTransportPlugin,       LINELIST, NULL),
   V(ClientTransportPlugin,       LINELIST, NULL),
   V(ClientUseIPv6,               BOOL,     "0"),
   V(ClientUseIPv6,               BOOL,     "0"),
+  V(ClientUseIPv4,               BOOL,     "1"),
   V(ConsensusParams,             STRING,   NULL),
   V(ConsensusParams,             STRING,   NULL),
   V(ConnLimit,                   UINT,     "1000"),
   V(ConnLimit,                   UINT,     "1000"),
   V(ConnDirectionStatistics,     BOOL,     "0"),
   V(ConnDirectionStatistics,     BOOL,     "0"),
@@ -3071,6 +3073,9 @@ options_validate(or_options_t *old_options, or_options_t *options,
     }
     }
   }
   }
 
 
+  /* Terminate Reachable*Addresses with reject *, but check if it has an
+   * IPv6 entry on the way through */
+  int reachable_knows_ipv6 = 0;
   for (i=0; i<3; i++) {
   for (i=0; i<3; i++) {
     config_line_t **linep =
     config_line_t **linep =
       (i==0) ? &options->ReachableAddresses :
       (i==0) ? &options->ReachableAddresses :
@@ -3080,7 +3085,19 @@ options_validate(or_options_t *old_options, or_options_t *options,
       continue;
       continue;
     /* We need to end with a reject *:*, not an implicit accept *:* */
     /* We need to end with a reject *:*, not an implicit accept *:* */
     for (;;) {
     for (;;) {
-      if (!strcmp((*linep)->value, "reject *:*")) /* already there */
+      /* Check if the policy has an IPv6 entry, or uses IPv4-specific
+       * policies (and therefore we assume it's aware of IPv6). */
+      if (!strcmpstart((*linep)->value, "accept6") ||
+          !strcmpstart((*linep)->value, "reject6") ||
+          !strstr((*linep)->value, "*6") ||
+          strchr((*linep)->value, '[') ||
+          !strcmpstart((*linep)->value, "accept4") ||
+          !strcmpstart((*linep)->value, "reject4") ||
+          !strstr((*linep)->value, "*4"))
+        reachable_knows_ipv6 = 1;
+       /* already has a reject all */
+      if (!strcmp((*linep)->value, "reject *:*") ||
+          !strcmp((*linep)->value, "reject *"))
         break;
         break;
       linep = &((*linep)->next);
       linep = &((*linep)->next);
       if (!*linep) {
       if (!*linep) {
@@ -3095,13 +3112,41 @@ options_validate(or_options_t *old_options, or_options_t *options,
     }
     }
   }
   }
 
 
-  if ((options->ReachableAddresses ||
+  if (options->ClientUseIPv6 &&
+      (options->ReachableAddresses ||
        options->ReachableORAddresses ||
        options->ReachableORAddresses ||
        options->ReachableDirAddresses) &&
        options->ReachableDirAddresses) &&
+      !reachable_knows_ipv6)
+    log_warn(LD_CONFIG, "You have set ClientUseIPv6 1 and at least one of "
+             "ReachableAddresses, ReachableORAddresses, or "
+             "ReachableDirAddresses, but without any IPv6-specific rules. "
+             "Tor won't connect to any IPv6 addresses, unless a rule accepts "
+             "them. (Use 'accept6 *:*' or 'reject6 *:*' as the last rule to "
+             "disable this warning.)");
+
+  if ((options->ReachableAddresses ||
+       options->ReachableORAddresses ||
+       options->ReachableDirAddresses ||
+       options->ClientUseIPv4 == 0) &&
       server_mode(options))
       server_mode(options))
     REJECT("Servers must be able to freely connect to the rest "
     REJECT("Servers must be able to freely connect to the rest "
            "of the Internet, so they must not set Reachable*Addresses "
            "of the Internet, so they must not set Reachable*Addresses "
-           "or FascistFirewall.");
+           "or FascistFirewall or FirewallPorts or ClientUseIPv4 0.");
+
+  /* We check if Reachable*Addresses blocks all addresses in
+   * parse_reachable_addresses(). */
+  if (options->ClientUseIPv4 == 0 && options->ClientUseIPv6 == 0)
+    REJECT("Tor cannot connect to the Internet if ClientUseIPv4 is 0 and "
+           "ClientUseIPv6 is 0. Please set at least one of these options "
+           "to 1.");
+
+  if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6ORPort == 1)
+    log_warn(LD_CONFIG, "ClientPreferIPv6ORPort 1 is ignored unless "
+             "ClientUseIPv6 is also 1.");
+
+  if (options->ClientUseIPv6 == 0 && options->ClientPreferIPv6DirPort == 1)
+    log_warn(LD_CONFIG, "ClientPreferIPv6DirPort 1 is ignored unless "
+             "ClientUseIPv6 is also 1.");
 
 
   if (options->UseBridges &&
   if (options->UseBridges &&
       server_mode(options))
       server_mode(options))

+ 2 - 0
src/or/connection.c

@@ -37,6 +37,7 @@
 #include "ext_orport.h"
 #include "ext_orport.h"
 #include "geoip.h"
 #include "geoip.h"
 #include "main.h"
 #include "main.h"
+#include "nodelist.h"
 #include "policies.h"
 #include "policies.h"
 #include "reasons.h"
 #include "reasons.h"
 #include "relay.h"
 #include "relay.h"
@@ -44,6 +45,7 @@
 #include "rendcommon.h"
 #include "rendcommon.h"
 #include "rephist.h"
 #include "rephist.h"
 #include "router.h"
 #include "router.h"
+#include "routerlist.h"
 #include "transports.h"
 #include "transports.h"
 #include "routerparse.h"
 #include "routerparse.h"
 #include "transports.h"
 #include "transports.h"

+ 4 - 4
src/or/directory.c

@@ -313,7 +313,6 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
   SMARTLIST_FOREACH_BEGIN(dirservers, dir_server_t *, ds) {
   SMARTLIST_FOREACH_BEGIN(dirservers, dir_server_t *, ds) {
       routerstatus_t *rs = &(ds->fake_status);
       routerstatus_t *rs = &(ds->fake_status);
       size_t upload_len = payload_len;
       size_t upload_len = payload_len;
-      tor_addr_t ds_addr;
 
 
       if ((type & ds->type) == 0)
       if ((type & ds->type) == 0)
         continue;
         continue;
@@ -344,11 +343,12 @@ directory_post_to_dirservers(uint8_t dir_purpose, uint8_t router_purpose,
         log_info(LD_DIR, "Uploading an extrainfo too (length %d)",
         log_info(LD_DIR, "Uploading an extrainfo too (length %d)",
                  (int) extrainfo_len);
                  (int) extrainfo_len);
       }
       }
-      tor_addr_from_ipv4h(&ds_addr, ds->addr);
       if (purpose_needs_anonymity(dir_purpose, router_purpose)) {
       if (purpose_needs_anonymity(dir_purpose, router_purpose)) {
         indirection = DIRIND_ANONYMOUS;
         indirection = DIRIND_ANONYMOUS;
-      } else if (!fascist_firewall_allows_address_dir(&ds_addr,ds->dir_port)) {
-        if (fascist_firewall_allows_address_or(&ds_addr,ds->or_port))
+      } else if (!fascist_firewall_allows_dir_server(ds,
+                                                     FIREWALL_DIR_CONNECTION,
+                                                     0)) {
+        if (fascist_firewall_allows_dir_server(ds, FIREWALL_OR_CONNECTION, 0))
           indirection = DIRIND_ONEHOP;
           indirection = DIRIND_ONEHOP;
         else
         else
           indirection = DIRIND_ANONYMOUS;
           indirection = DIRIND_ANONYMOUS;

+ 5 - 3
src/or/entrynodes.c

@@ -268,7 +268,7 @@ entry_is_live(const entry_guard_t *e, entry_is_live_flags_t flags,
     *msg = "not fast/stable";
     *msg = "not fast/stable";
     return NULL;
     return NULL;
   }
   }
-  if (!fascist_firewall_allows_node(node)) {
+  if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION, 0)) {
     *msg = "unreachable by config";
     *msg = "unreachable by config";
     return NULL;
     return NULL;
   }
   }
@@ -918,7 +918,8 @@ entry_guards_set_from_config(const or_options_t *options)
     } else if (routerset_contains_node(options->ExcludeNodes, node)) {
     } else if (routerset_contains_node(options->ExcludeNodes, node)) {
       SMARTLIST_DEL_CURRENT(entry_nodes, node);
       SMARTLIST_DEL_CURRENT(entry_nodes, node);
       continue;
       continue;
-    } else if (!fascist_firewall_allows_node(node)) {
+    } else if (!fascist_firewall_allows_node(node, FIREWALL_OR_CONNECTION,
+                                             0)) {
       SMARTLIST_DEL_CURRENT(entry_nodes, node);
       SMARTLIST_DEL_CURRENT(entry_nodes, node);
       continue;
       continue;
     } else if (! node->is_possible_guard) {
     } else if (! node->is_possible_guard) {
@@ -2178,7 +2179,8 @@ fetch_bridge_descriptors(const or_options_t *options, time_t now)
                 !options->UpdateBridgesFromAuthority, !num_bridge_auths);
                 !options->UpdateBridgesFromAuthority, !num_bridge_auths);
 
 
       if (ask_bridge_directly &&
       if (ask_bridge_directly &&
-          !fascist_firewall_allows_address_or(&bridge->addr, bridge->port)) {
+          !fascist_firewall_allows_address_addr(&bridge->addr, bridge->port,
+                                                FIREWALL_OR_CONNECTION, 0)) {
         log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
         log_notice(LD_DIR, "Bridge at '%s' isn't reachable by our "
                    "firewall policy. %s.",
                    "firewall policy. %s.",
                    fmt_addrport(&bridge->addr, bridge->port),
                    fmt_addrport(&bridge->addr, bridge->port),

+ 218 - 45
src/or/nodelist.c

@@ -214,6 +214,76 @@ nodelist_add_microdesc(microdesc_t *md)
   return node;
   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>.
 /** Tell the nodelist that the current usable consensus is <b>ns</b>.
  * This makes the nodelist change all of the routerstatus entries for
  * This makes the nodelist change all of the routerstatus entries for
  * the nodes, drop nodes that no longer have enough info to get used,
  * the nodes, drop nodes that no longer have enough info to get used,
@@ -224,7 +294,6 @@ nodelist_set_consensus(networkstatus_t *ns)
 {
 {
   const or_options_t *options = get_options();
   const or_options_t *options = get_options();
   int authdir = authdir_mode_v3(options);
   int authdir = authdir_mode_v3(options);
-  int client = !server_mode(options);
 
 
   init_nodelist();
   init_nodelist();
   if (ns->flavor == FLAV_MICRODESC)
   if (ns->flavor == FLAV_MICRODESC)
@@ -261,7 +330,7 @@ nodelist_set_consensus(networkstatus_t *ns)
       node->is_bad_exit = rs->is_bad_exit;
       node->is_bad_exit = rs->is_bad_exit;
       node->is_hs_dir = rs->is_hs_dir;
       node->is_hs_dir = rs->is_hs_dir;
       node->ipv6_preferred = 0;
       node->ipv6_preferred = 0;
-      if (client && options->ClientPreferIPv6ORPort == 1 &&
+      if (nodelist_prefer_ipv6_orport(options) &&
           (tor_addr_is_null(&rs->ipv6_addr) == 0 ||
           (tor_addr_is_null(&rs->ipv6_addr) == 0 ||
            (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0)))
            (node->md && tor_addr_is_null(&node->md->ipv6_addr) == 0)))
         node->ipv6_preferred = 1;
         node->ipv6_preferred = 1;
@@ -925,30 +994,60 @@ node_get_declared_family(const node_t *node)
     return NULL;
     return NULL;
 }
 }
 
 
+/* Does this node have a valid IPv6 address? */
+static int
+node_has_ipv6_addr(const node_t *node)
+{
+  if (node->ri)
+    return !tor_addr_is_null(&node->ri->ipv6_addr);
+  if (node->md)
+    return !tor_addr_is_null(&node->md->ipv6_addr);
+  if (node->rs)
+    return !tor_addr_is_null(&node->rs->ipv6_addr);
+
+  return 0;
+}
+
 /** Return 1 if we prefer the IPv6 address and OR TCP port of
 /** Return 1 if we prefer the IPv6 address and OR TCP port of
  * <b>node</b>, else 0.
  * <b>node</b>, else 0.
  *
  *
- *  We prefer the IPv6 address if the router has an IPv6 address and
+ *  We prefer the IPv6 address if the router has an IPv6 address,
+ *  and we can use IPv6 addresses, and:
  *  i) the node_t says that it prefers IPv6
  *  i) the node_t says that it prefers IPv6
  *  or
  *  or
- *  ii) the router has no IPv4 address. */
+ *  ii) the router has no IPv4 OR address.
+ *  or
+ *  iii) our preference is for IPv6 addresses.
+ *  (This extra step is needed in case our preferences have changed since
+ *  node->ipv6_preferred was set at the time the consensus was loaded.)
+ */
 int
 int
-node_ipv6_preferred(const node_t *node)
+node_ipv6_or_preferred(const node_t *node)
 {
 {
+  const or_options_t *options = get_options();
   tor_addr_port_t ipv4_addr;
   tor_addr_port_t ipv4_addr;
   node_assert_ok(node);
   node_assert_ok(node);
 
 
-  if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr)) {
-    if (node->ri)
-      return !tor_addr_is_null(&node->ri->ipv6_addr);
-    if (node->md)
-      return !tor_addr_is_null(&node->md->ipv6_addr);
-    if (node->rs)
-      return !tor_addr_is_null(&node->rs->ipv6_addr);
+  if (!options->ClientUseIPv6) {
+    return 0;
+  } else if (node->ipv6_preferred || node_get_prim_orport(node, &ipv4_addr)
+      || nodelist_prefer_ipv6_orport(get_options())) {
+    return node_has_ipv6_addr(node);
   }
   }
   return 0;
   return 0;
 }
 }
 
 
+#define RETURN_IPV4_AP(r, port_field, ap_out) \
+  STMT_BEGIN \
+    if (r) { \
+      if ((r)->addr == 0 || (r)->port_field == 0) \
+        return -1; \
+      tor_addr_from_ipv4h(&(ap_out)->addr, (r)->addr); \
+      (ap_out)->port = (r)->port_field; \
+      return 0; \
+    } \
+  STMT_END
+
 /** Copy the primary (IPv4) OR port (IP address and TCP port) for
 /** Copy the primary (IPv4) OR port (IP address and TCP port) for
  * <b>node</b> into *<b>ap_out</b>. Return 0 if a valid address and
  * <b>node</b> into *<b>ap_out</b>. Return 0 if a valid address and
  * port was copied, else return non-zero.*/
  * port was copied, else return non-zero.*/
@@ -958,20 +1057,10 @@ node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out)
   node_assert_ok(node);
   node_assert_ok(node);
   tor_assert(ap_out);
   tor_assert(ap_out);
 
 
-  if (node->ri) {
-    if (node->ri->addr == 0 || node->ri->or_port == 0)
-      return -1;
-    tor_addr_from_ipv4h(&ap_out->addr, node->ri->addr);
-    ap_out->port = node->ri->or_port;
-    return 0;
-  }
-  if (node->rs) {
-    if (node->rs->addr == 0 || node->rs->or_port == 0)
-      return -1;
-    tor_addr_from_ipv4h(&ap_out->addr, node->rs->addr);
-    ap_out->port = node->rs->or_port;
-    return 0;
-  }
+  RETURN_IPV4_AP(node->ri, or_port, ap_out);
+  RETURN_IPV4_AP(node->rs, or_port, ap_out);
+  /* Microdescriptors only have an IPv6 address */
+
   return -1;
   return -1;
 }
 }
 
 
@@ -980,21 +1069,12 @@ node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out)
 void
 void
 node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out)
 node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out)
 {
 {
-  const or_options_t *options = get_options();
   tor_assert(ap_out);
   tor_assert(ap_out);
 
 
-  /* Cheap implementation of config option ClientUseIPv6 -- simply
-     don't prefer IPv6 when ClientUseIPv6 is not set and we're not a
-     client running with bridges. See #4455 for more on this subject.
-
-     Note that this filter is too strict since we're hindering not
-     only clients! Erring on the safe side shouldn't be a problem
-     though. XXX move this check to where outgoing connections are
-     made? -LN */
-  if ((options->ClientUseIPv6 || options->UseBridges) &&
-      node_ipv6_preferred(node)) {
+  if (node_ipv6_or_preferred(node)) {
     node_get_pref_ipv6_orport(node, ap_out);
     node_get_pref_ipv6_orport(node, ap_out);
   } else {
   } else {
+    /* the primary ORPort is always on IPv4 */
     node_get_prim_orport(node, ap_out);
     node_get_prim_orport(node, ap_out);
   }
   }
 }
 }
@@ -1007,20 +1087,113 @@ node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out)
   node_assert_ok(node);
   node_assert_ok(node);
   tor_assert(ap_out);
   tor_assert(ap_out);
 
 
-  /* We prefer the microdesc over a potential routerstatus here. They
-     are not being synchronised atm so there might be a chance that
-     they differ at some point, f.ex. when flipping
-     UseMicrodescriptors? -LN */
+  /* Prefer routerstatus over microdesc for consistency with the
+   * fascist_firewall_* functions. Also check if the address or port are valid,
+   * and try another alternative if they are not. */
 
 
-  if (node->ri) {
+  if (node->ri && node->ri->ipv6_orport
+      && !tor_addr_is_null(&node->ri->ipv6_addr)) {
     tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr);
     tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr);
     ap_out->port = node->ri->ipv6_orport;
     ap_out->port = node->ri->ipv6_orport;
-  } else if (node->md) {
+  } else if (node->rs && node->rs->ipv6_orport
+             && !tor_addr_is_null(&node->rs->ipv6_addr)) {
+    tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr);
+    ap_out->port = node->rs->ipv6_orport;
+  } else if (node->md && node->md->ipv6_orport
+             && !tor_addr_is_null(&node->md->ipv6_addr)) {
     tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr);
     tor_addr_copy(&ap_out->addr, &node->md->ipv6_addr);
     ap_out->port = node->md->ipv6_orport;
     ap_out->port = node->md->ipv6_orport;
-  } else if (node->rs) {
+  } else {
+    tor_addr_make_null(&ap_out->addr, AF_INET6);
+    ap_out->port = 0;
+  }
+}
+
+/** Return 1 if we prefer the IPv6 address and Dir TCP port of
+ * <b>node</b>, else 0.
+ *
+ *  We prefer the IPv6 address if the router has an IPv6 address,
+ *  and we can use IPv6 addresses, and:
+ *  i) the node_t says that it prefers IPv6
+ *  or
+ *  ii) the router has no IPv4 Dir address.
+ *  or
+ *  iii) our preference is for IPv6 addresses.
+ *  (This extra step is needed in case our preferences have changed since
+ *  node->ipv6_preferred was set at the time the consensus was loaded.)
+ */
+int
+node_ipv6_dir_preferred(const node_t *node)
+{
+  const or_options_t *options = get_options();
+  tor_addr_port_t ipv4_addr;
+  node_assert_ok(node);
+
+  if (!options->ClientUseIPv6) {
+    return 0;
+  } else if (node->ipv6_preferred || node_get_prim_dirport(node, &ipv4_addr)
+      || nodelist_prefer_ipv6_dirport(get_options())) {
+    return node_has_ipv6_addr(node);
+  }
+  return 0;
+}
+
+/** Copy the primary (IPv4) Dir port (IP address and TCP port) for
+ * <b>node</b> into *<b>ap_out</b>. Return 0 if a valid address and
+ * port was copied, else return non-zero.*/
+int
+node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out)
+{
+  node_assert_ok(node);
+  tor_assert(ap_out);
+
+  RETURN_IPV4_AP(node->ri, dir_port, ap_out);
+  RETURN_IPV4_AP(node->rs, dir_port, ap_out);
+  /* Microdescriptors only have an IPv6 address */
+
+  return -1;
+}
+
+#undef RETURN_IPV4_AP
+
+/** Copy the preferred Dir port (IP address and TCP port) for
+ * <b>node</b> into *<b>ap_out</b>.  */
+void
+node_get_pref_dirport(const node_t *node, tor_addr_port_t *ap_out)
+{
+  tor_assert(ap_out);
+
+  if (node_ipv6_dir_preferred(node)) {
+    node_get_pref_ipv6_dirport(node, ap_out);
+  } else {
+    /* the primary DirPort is always on IPv4 */
+    node_get_prim_dirport(node, ap_out);
+  }
+}
+
+/** Copy the preferred IPv6 Dir port (IP address and TCP port) for
+ * <b>node</b> into *<b>ap_out</b>. */
+void
+node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out)
+{
+  node_assert_ok(node);
+  tor_assert(ap_out);
+
+  /* Check if the address or port are valid, and try another alternative if
+   * they are not. Note that microdescriptors have no dir_port. */
+
+  /* Assume IPv4 and IPv6 dirports are the same */
+  if (node->ri && node->ri->dir_port
+      && !tor_addr_is_null(&node->ri->ipv6_addr)) {
+    tor_addr_copy(&ap_out->addr, &node->ri->ipv6_addr);
+    ap_out->port = node->ri->dir_port;
+  } else if (node->rs && node->rs->dir_port
+             && !tor_addr_is_null(&node->rs->ipv6_addr)) {
     tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr);
     tor_addr_copy(&ap_out->addr, &node->rs->ipv6_addr);
-    ap_out->port = node->rs->ipv6_orport;
+    ap_out->port = node->rs->dir_port;
+  } else {
+    tor_addr_make_null(&ap_out->addr, AF_INET6);
+    ap_out->port = 0;
   }
   }
 }
 }
 
 

+ 10 - 1
src/or/nodelist.h

@@ -21,6 +21,8 @@ MOCK_DECL(const node_t *, node_get_by_id, (const char *identity_digest));
 const node_t *node_get_by_hex_id(const char *identity_digest);
 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_set_routerinfo(routerinfo_t *ri, routerinfo_t **ri_old_out);
 node_t *nodelist_add_microdesc(microdesc_t *md);
 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_set_consensus(networkstatus_t *ns);
 
 
 void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md);
 void nodelist_remove_microdesc(const char *identity_digest, microdesc_t *md);
@@ -55,10 +57,17 @@ void node_get_address_string(const node_t *node, char *cp, size_t len);
 long node_get_declared_uptime(const node_t *node);
 long node_get_declared_uptime(const node_t *node);
 time_t node_get_published_on(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);
 const smartlist_t *node_get_declared_family(const node_t *node);
-int node_ipv6_preferred(const node_t *node);
+
+/* Deprecated - use node_ipv6_or_preferred or node_ipv6_dir_preferred */
+#define node_ipv6_preferred(node) node_ipv6_or_preferred(node)
+int node_ipv6_or_preferred(const node_t *node);
 int node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out);
 int node_get_prim_orport(const node_t *node, tor_addr_port_t *ap_out);
 void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out);
 void node_get_pref_orport(const node_t *node, tor_addr_port_t *ap_out);
 void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out);
 void node_get_pref_ipv6_orport(const node_t *node, tor_addr_port_t *ap_out);
+int node_ipv6_dir_preferred(const node_t *node);
+int node_get_prim_dirport(const node_t *node, tor_addr_port_t *ap_out);
+void node_get_pref_dirport(const node_t *node, tor_addr_port_t *ap_out);
+void node_get_pref_ipv6_dirport(const node_t *node, tor_addr_port_t *ap_out);
 int node_has_curve25519_onion_key(const node_t *node);
 int node_has_curve25519_onion_key(const node_t *node);
 
 
 MOCK_DECL(smartlist_t *, nodelist_get_list, (void));
 MOCK_DECL(smartlist_t *, nodelist_get_list, (void));

+ 16 - 5
src/or/or.h

@@ -2400,7 +2400,8 @@ typedef struct node_t {
 
 
   /* Local info: derived. */
   /* Local info: derived. */
 
 
-  /** True if the IPv6 OR port is preferred over the IPv4 OR port.  */
+  /** True if the IPv6 OR port is preferred over the IPv4 OR port.
+   * XX/teor - can this become out of date if the torrc changes? */
   unsigned int ipv6_preferred:1;
   unsigned int ipv6_preferred:1;
 
 
   /** According to the geoip db what country is this router in? */
   /** According to the geoip db what country is this router in? */
@@ -4061,12 +4062,20 @@ typedef struct {
    * over randomly chosen exits. */
    * over randomly chosen exits. */
   int ClientRejectInternalAddresses;
   int ClientRejectInternalAddresses;
 
 
-  /** If true, clients may connect over IPv6. XXX we don't really
-      enforce this -- clients _may_ set up outgoing IPv6 connections
-      even when this option is not set. */
+  /** If true, clients may connect over IPv4. If false, they will avoid
+   * connecting over IPv4. We enforce this for OR and Dir connections. */
+  int ClientUseIPv4;
+  /** If true, clients may connect over IPv6. If false, they will avoid
+   * connecting over IPv4. We enforce this for OR and Dir connections. */
   int ClientUseIPv6;
   int ClientUseIPv6;
-  /** If true, prefer an IPv6 OR port over an IPv4 one. */
+  /** If true, prefer an IPv6 OR port over an IPv4 one for entry node
+   * connections. Use nodelist_prefer_ipv6_orport() instead of accessing
+   * this value directly. */
   int ClientPreferIPv6ORPort;
   int ClientPreferIPv6ORPort;
+  /** If true, prefer an IPv6 directory port over an IPv4 one for direct
+   * directory connections. Use nodelist_prefer_ipv6_dirport() instead of
+   * accessing this value directly.  */
+  int ClientPreferIPv6DirPort;
 
 
   /** The length of time that we think a consensus should be fresh. */
   /** The length of time that we think a consensus should be fresh. */
   int V3AuthVotingInterval;
   int V3AuthVotingInterval;
@@ -5125,6 +5134,8 @@ typedef struct dir_server_t {
   char *description;
   char *description;
   char *nickname;
   char *nickname;
   char *address; /**< Hostname. */
   char *address; /**< Hostname. */
+  /* XX/teor - why do we duplicate the address and port fields here and in
+   *           fake_status? Surely we could just use fake_status (#17867). */
   tor_addr_t ipv6_addr; /**< IPv6 address if present; AF_UNSPEC if not */
   tor_addr_t ipv6_addr; /**< IPv6 address if present; AF_UNSPEC if not */
   uint32_t addr; /**< IPv4 address. */
   uint32_t addr; /**< IPv4 address. */
   uint16_t dir_port; /**< Directory port. */
   uint16_t dir_port; /**< Directory port. */

+ 700 - 35
src/or/policies.c

@@ -13,6 +13,7 @@
 #include "or.h"
 #include "or.h"
 #include "config.h"
 #include "config.h"
 #include "dirserv.h"
 #include "dirserv.h"
+#include "networkstatus.h"
 #include "nodelist.h"
 #include "nodelist.h"
 #include "policies.h"
 #include "policies.h"
 #include "router.h"
 #include "router.h"
@@ -270,16 +271,21 @@ parse_reachable_addresses(void)
                "Error parsing ReachableDirAddresses entry; ignoring.");
                "Error parsing ReachableDirAddresses entry; ignoring.");
     ret = -1;
     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 <b>policy</b> (possibly NULL) will allow a
 /** Return true iff <b>policy</b> (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 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,
   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
- * <b>ri</b>. */
+/** 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
 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
- * <b>node</b>. */
+/* 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
 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
+ * <b>fw_connection</b>.
+ * 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
+ * <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 {
   } 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
+ * <b>rs</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 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;
     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
+ * <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
 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
+ * <b>node</b>:
+ *  - if <b>preferred</b> 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 <b>fw_connection</b>.
+ * 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
+ * <b>ds</b> on either its IPv4 or IPv6 address. Uses ReachableORAddresses or
+ * ReachableDirAddresses based on <b>fw_connection</b> (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 <b>ap</b> 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
+ * <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. */
+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 <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
+ * 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 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 <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
+ * 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. */
+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 <b>ds</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. */
+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 <b>addr</b> is permitted to connect to our dir port,
 /** Return 1 if <b>addr</b> is permitted to connect to our dir port,

+ 57 - 5
src/or/policies.h

@@ -22,13 +22,61 @@
 #define EXIT_POLICY_REJECT_PRIVATE (1 << 1)
 #define EXIT_POLICY_REJECT_PRIVATE (1 << 1)
 #define EXIT_POLICY_ADD_DEFAULT    (1 << 2)
 #define EXIT_POLICY_ADD_DEFAULT    (1 << 2)
 
 
+typedef enum firewall_connection_t {
+  FIREWALL_OR_CONNECTION      = 0,
+  FIREWALL_DIR_CONNECTION     = 1
+} firewall_connection_t;
+
 typedef int exit_policy_parser_cfg_t;
 typedef int exit_policy_parser_cfg_t;
 
 
-int firewall_is_fascist_or(void);
-int fascist_firewall_allows_address_or(const tor_addr_t *addr, uint16_t port);
-int fascist_firewall_allows_or(const routerinfo_t *ri);
-int fascist_firewall_allows_node(const node_t *node);
-int fascist_firewall_allows_address_dir(const tor_addr_t *addr, uint16_t port);
+int fascist_firewall_allows_address_addr(const tor_addr_t *addr, uint16_t port,
+                                         firewall_connection_t fw_connection,
+                                         int pref_only);
+int fascist_firewall_allows_address_ap(const tor_addr_port_t *ap,
+                                       firewall_connection_t fw_connection,
+                                       int pref_only);
+int fascist_firewall_allows_address_ipv4h(uint32_t ipv4h_or_addr,
+                                          uint16_t ipv4_or_port,
+                                          firewall_connection_t fw_connection,
+                                          int pref_only);
+int fascist_firewall_allows_ri(const routerinfo_t *ri,
+                               firewall_connection_t fw_connection,
+                               int pref_only);
+int fascist_firewall_allows_rs(const routerstatus_t *rs,
+                               firewall_connection_t fw_connection,
+                               int pref_only);
+int fascist_firewall_allows_md(const microdesc_t *md,
+                               firewall_connection_t fw_connection,
+                               int pref_only);
+int fascist_firewall_allows_node(const node_t *node,
+                                 firewall_connection_t fw_connection,
+                                 int pref_only);
+int fascist_firewall_allows_dir_server(const dir_server_t *ds,
+                                       firewall_connection_t fw_connection,
+                                       int pref_only);
+
+const tor_addr_port_t * fascist_firewall_choose_address(
+                                          const tor_addr_port_t *a,
+                                          const tor_addr_port_t *b,
+                                          int want_a,
+                                          firewall_connection_t fw_connection,
+                                          int pref_only);
+int fascist_firewall_choose_address_ri(const routerinfo_t *ri,
+                                       firewall_connection_t fw_connection,
+                                       int pref_only, tor_addr_port_t* ap);
+int fascist_firewall_choose_address_rs(const routerstatus_t *rs,
+                                       firewall_connection_t fw_connection,
+                                       int pref_only, tor_addr_port_t* ap);
+int fascist_firewall_choose_address_md(const microdesc_t *md,
+                                       firewall_connection_t fw_connection,
+                                       int pref_only, tor_addr_port_t* ap);
+int fascist_firewall_choose_address_node(const node_t *node,
+                                         firewall_connection_t fw_connection,
+                                         int pref_only, tor_addr_port_t* ap);
+int fascist_firewall_choose_address_dir_server(const dir_server_t *ds,
+                                          firewall_connection_t fw_connection,
+                                           int pref_only, tor_addr_port_t* ap);
+
 int dir_policy_permits_address(const tor_addr_t *addr);
 int dir_policy_permits_address(const tor_addr_t *addr);
 int socks_policy_permits_address(const tor_addr_t *addr);
 int socks_policy_permits_address(const tor_addr_t *addr);
 int authdir_policy_permits_address(uint32_t addr, uint16_t port);
 int authdir_policy_permits_address(uint32_t addr, uint16_t port);
@@ -94,6 +142,10 @@ addr_policy_result_t compare_tor_addr_to_short_policy(
 
 
 #ifdef POLICIES_PRIVATE
 #ifdef POLICIES_PRIVATE
 STATIC void append_exit_policy_string(smartlist_t **policy, const char *more);
 STATIC void append_exit_policy_string(smartlist_t **policy, const char *more);
+STATIC int fascist_firewall_allows_address(const tor_addr_t *addr,
+                                           uint16_t port,
+                                           smartlist_t *firewall_policy,
+                                           int pref_only, int pref_ipv6);
 #endif
 #endif
 
 
 #endif
 #endif

+ 97 - 0
src/test/test_entrynodes.c

@@ -624,6 +624,100 @@ test_entry_is_live(void *arg)
   ; /* XXX */
   ; /* XXX */
 }
 }
 
 
+static or_options_t mocked_options;
+
+static const or_options_t *
+mock_get_options(void)
+{
+  return &mocked_options;
+}
+
+#define TEST_IPV4_ADDR "123.45.67.89"
+#define TEST_IPV6_ADDR "[1234:5678:90ab:cdef::]"
+
+static void
+test_node_preferred_orport(void *arg)
+{
+  (void)arg;
+  tor_addr_t ipv4_addr;
+  const uint16_t ipv4_port = 4444;
+  tor_addr_t ipv6_addr;
+  const uint16_t ipv6_port = 6666;
+  routerinfo_t node_ri;
+  node_t node;
+  tor_addr_port_t ap;
+
+  /* Setup options */
+  memset(&mocked_options, 0, sizeof(mocked_options));
+  /* We don't test ClientPreferIPv6ORPort here, because it's only used in
+   * nodelist_set_consensus to setup each node_t. */
+  MOCK(get_options, mock_get_options);
+
+  /* Setup IP addresses */
+  tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR);
+  tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR);
+
+  /* Setup node_ri */
+  memset(&node_ri, 0, sizeof(node_ri));
+  node_ri.addr = tor_addr_to_ipv4h(&ipv4_addr);
+  node_ri.or_port = ipv4_port;
+  tor_addr_copy(&node_ri.ipv6_addr, &ipv6_addr);
+  node_ri.ipv6_orport = ipv6_port;
+
+  /* Setup node */
+  memset(&node, 0, sizeof(node));
+  node.ri = &node_ri;
+
+  /* Check the preferred address is IPv4 if we're only using IPv4, regardless
+   * of whether we prefer it or not */
+  mocked_options.ClientUseIPv4 = 1;
+  mocked_options.ClientUseIPv6 = 0;
+  node.ipv6_preferred = 0;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+  tt_assert(ap.port == ipv4_port);
+
+  node.ipv6_preferred = 1;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+  tt_assert(ap.port == ipv4_port);
+
+  /* Check the preferred address is IPv4 if we're using IPv4 and IPv6, but
+   * don't prefer the IPv6 address */
+  mocked_options.ClientUseIPv4 = 1;
+  mocked_options.ClientUseIPv6 = 1;
+  node.ipv6_preferred = 0;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv4_addr));
+  tt_assert(ap.port == ipv4_port);
+
+  /* Check the preferred address is IPv6 if we prefer it and
+   * ClientUseIPv6 is 1, regardless of ClientUseIPv4 */
+  mocked_options.ClientUseIPv4 = 1;
+  mocked_options.ClientUseIPv6 = 1;
+  node.ipv6_preferred = 1;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+  tt_assert(ap.port == ipv6_port);
+
+  mocked_options.ClientUseIPv4 = 0;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+  tt_assert(ap.port == ipv6_port);
+
+  /* Check the preferred address is IPv6 if we don't prefer it, but
+   * ClientUseIPv4 is 0 */
+  mocked_options.ClientUseIPv4 = 0;
+  mocked_options.ClientUseIPv6 = 1;
+  node.ipv6_preferred = 0;
+  node_get_pref_orport(&node, &ap);
+  tt_assert(tor_addr_eq(&ap.addr, &ipv6_addr));
+  tt_assert(ap.port == ipv6_port);
+
+ done:
+  UNMOCK(get_options);
+}
+
 static const struct testcase_setup_t fake_network = {
 static const struct testcase_setup_t fake_network = {
   fake_network_setup, fake_network_cleanup
   fake_network_setup, fake_network_cleanup
 };
 };
@@ -654,6 +748,9 @@ struct testcase_t entrynodes_tests[] = {
   { "entry_is_live",
   { "entry_is_live",
     test_entry_is_live,
     test_entry_is_live,
     TT_FORK, &fake_network, NULL },
     TT_FORK, &fake_network, NULL },
+  { "node_preferred_orport",
+    test_node_preferred_orport,
+    0, NULL, NULL },
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };
 
 

+ 447 - 0
src/test/test_policy.c

@@ -1127,6 +1127,449 @@ test_policies_getinfo_helper_policies(void *arg)
 #undef TEST_IPV4_ADDR
 #undef TEST_IPV4_ADDR
 #undef TEST_IPV6_ADDR
 #undef TEST_IPV6_ADDR
 
 
+#define TEST_IPV4_ADDR_STR "1.2.3.4"
+#define TEST_IPV6_ADDR_STR "[1002::4567]"
+#define REJECT_IPv4_FINAL_STR "reject 0.0.0.0/0:*"
+#define REJECT_IPv6_FINAL_STR "reject [::]/0:*"
+
+#define OTHER_IPV4_ADDR_STR "6.7.8.9"
+#define OTHER_IPV6_ADDR_STR "[afff::]"
+
+/** Run unit tests for fascist_firewall_allows_address */
+static void
+test_policies_fascist_firewall_allows_address(void *arg)
+{
+  (void)arg;
+  tor_addr_t ipv4_addr, ipv6_addr, r_ipv4_addr, r_ipv6_addr;
+  tor_addr_t n_ipv4_addr, n_ipv6_addr;
+  const uint16_t port = 1234;
+  smartlist_t *policy = NULL;
+  smartlist_t *e_policy = NULL;
+  addr_policy_t *item = NULL;
+  int malformed_list = 0;
+
+  /* Setup the options and the items in the policies */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  MOCK(get_options, mock_get_options);
+
+  policy = smartlist_new();
+  item = router_parse_addr_policy_item_from_string("accept "
+                                                   TEST_IPV4_ADDR_STR ":*",
+                                                   ADDR_POLICY_ACCEPT,
+                                                   &malformed_list);
+  tt_assert(item);
+  tt_assert(!malformed_list);
+  smartlist_add(policy, item);
+  item = router_parse_addr_policy_item_from_string("accept "
+                                                   TEST_IPV6_ADDR_STR,
+                                                   ADDR_POLICY_ACCEPT,
+                                                   &malformed_list);
+  tt_assert(item);
+  tt_assert(!malformed_list);
+  smartlist_add(policy, item);
+  /* Normally, policy_expand_unspec would do this for us */
+  item = router_parse_addr_policy_item_from_string(REJECT_IPv4_FINAL_STR,
+                                                   ADDR_POLICY_ACCEPT,
+                                                   &malformed_list);
+  tt_assert(item);
+  tt_assert(!malformed_list);
+  smartlist_add(policy, item);
+  item = router_parse_addr_policy_item_from_string(REJECT_IPv6_FINAL_STR,
+                                                   ADDR_POLICY_ACCEPT,
+                                                   &malformed_list);
+  tt_assert(item);
+  tt_assert(!malformed_list);
+  smartlist_add(policy, item);
+  item = NULL;
+
+  e_policy = smartlist_new();
+
+  /*
+  char *polstr = policy_dump_to_string(policy, 1, 1);
+  printf("%s\n", polstr);
+  tor_free(polstr);
+   */
+
+  /* Parse the addresses */
+  tor_addr_parse(&ipv4_addr, TEST_IPV4_ADDR_STR);
+  tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR_STR);
+  tor_addr_parse(&r_ipv4_addr, OTHER_IPV4_ADDR_STR);
+  tor_addr_parse(&r_ipv6_addr, OTHER_IPV6_ADDR_STR);
+  tor_addr_make_null(&n_ipv4_addr, AF_INET);
+  tor_addr_make_null(&n_ipv6_addr, AF_INET6);
+
+  /* Test the function's address matching with IPv4 and IPv6 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* Preferring IPv4 */
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 0)
+            == 0);
+
+  /* Preferring IPv6 */
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 1, 1)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 1, 1)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 1, 1)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 1, 1)
+            == 0);
+
+  /* Test the function's address matching with UseBridges on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 0;
+  mock_options.ClientUseIPv6 = 0;
+  mock_options.UseBridges = 1;
+
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* Test the function's address matching with IPv4 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 0;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* Test the function's address matching with IPv6 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 0;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* Test the function's address matching with everything off */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 0;
+  mock_options.ClientUseIPv6 = 0;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&r_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* Test the function's address matching for unusual inputs */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 1;
+
+  /* NULL and tor_addr_is_null addresses are rejected */
+  tt_assert(fascist_firewall_allows_address(NULL, port, policy, 0, 0) == 0);
+  tt_assert(fascist_firewall_allows_address(&n_ipv4_addr, port, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&n_ipv6_addr, port, policy, 0, 0)
+            == 0);
+
+  /* zero ports are rejected */
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, 0, policy, 0, 0)
+            == 0);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, 0, policy, 0, 0)
+            == 0);
+
+  /* NULL and empty policies accept everything */
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, NULL, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, NULL, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv4_addr, port, e_policy, 0, 0)
+            == 1);
+  tt_assert(fascist_firewall_allows_address(&ipv6_addr, port, e_policy, 0, 0)
+            == 1);
+
+ done:
+  addr_policy_free(item);
+  addr_policy_list_free(policy);
+  addr_policy_list_free(e_policy);
+  UNMOCK(get_options);
+}
+
+#undef REJECT_IPv4_FINAL_STR
+#undef REJECT_IPv6_FINAL_STR
+#undef OTHER_IPV4_ADDR_STR
+#undef OTHER_IPV6_ADDR_STR
+
+#define TEST_IPV4_OR_PORT  1234
+#define TEST_IPV4_DIR_PORT 2345
+#define TEST_IPV6_OR_PORT  61234
+#define TEST_IPV6_DIR_PORT 62345
+
+/** Run unit tests for fascist_firewall_choose_address */
+static void
+test_policies_fascist_firewall_choose_address(void *arg)
+{
+  (void)arg;
+  tor_addr_port_t ipv4_or_ap, ipv4_dir_ap, ipv6_or_ap, ipv6_dir_ap;
+  tor_addr_port_t n_ipv4_ap, n_ipv6_ap;
+
+  /* Setup the options */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  MOCK(get_options, mock_get_options);
+
+  /* Parse the addresses */
+  tor_addr_parse(&ipv4_or_ap.addr, TEST_IPV4_ADDR_STR);
+  ipv4_or_ap.port = TEST_IPV4_OR_PORT;
+  tor_addr_parse(&ipv4_dir_ap.addr, TEST_IPV4_ADDR_STR);
+  ipv4_dir_ap.port = TEST_IPV4_DIR_PORT;
+
+  tor_addr_parse(&ipv6_or_ap.addr, TEST_IPV6_ADDR_STR);
+  ipv6_or_ap.port = TEST_IPV6_OR_PORT;
+  tor_addr_parse(&ipv6_dir_ap.addr, TEST_IPV6_ADDR_STR);
+  ipv6_dir_ap.port = TEST_IPV6_DIR_PORT;
+
+  tor_addr_make_null(&n_ipv4_ap.addr, AF_INET);
+  n_ipv4_ap.port = 0;
+  tor_addr_make_null(&n_ipv6_ap.addr, AF_INET6);
+  n_ipv6_ap.port = 0;
+
+  /* Choose an address with IPv4 and IPv6 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 0;
+
+  /* Preferring IPv4 */
+  mock_options.ClientPreferIPv6ORPort = 0;
+  mock_options.ClientPreferIPv6DirPort = 0;
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv4_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv4_dir_ap);
+
+  /* Preferring IPv6 */
+  mock_options.ClientPreferIPv6ORPort = 1;
+  mock_options.ClientPreferIPv6DirPort = 1;
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv6_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv6_dir_ap);
+
+  /* Preferring IPv4 OR / IPv6 Dir */
+  mock_options.ClientPreferIPv6ORPort = 0;
+  mock_options.ClientPreferIPv6DirPort = 1;
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv6_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv6_dir_ap);
+
+  /* Preferring IPv6 OR / IPv4 Dir */
+  mock_options.ClientPreferIPv6ORPort = 1;
+  mock_options.ClientPreferIPv6DirPort = 0;
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv4_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv4_dir_ap);
+
+  /* Choose an address with UseBridges on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.UseBridges = 1;
+
+  for (mock_options.ClientUseIPv4 = 0; mock_options.ClientUseIPv4 <= 1;
+       mock_options.ClientUseIPv4++) {
+    for (mock_options.ClientUseIPv6 = 0; mock_options.ClientUseIPv6 <= 1;
+         mock_options.ClientUseIPv6++) {
+      for (mock_options.ClientPreferIPv6ORPort = 0;
+           mock_options.ClientPreferIPv6ORPort <= 1;
+           mock_options.ClientPreferIPv6ORPort++) {
+        for (mock_options.ClientPreferIPv6DirPort = 0;
+             mock_options.ClientPreferIPv6DirPort <= 1;
+             mock_options.ClientPreferIPv6DirPort++) {
+          /* This (ab)uses the actual enum values */
+          tt_assert(FIREWALL_OR_CONNECTION < FIREWALL_DIR_CONNECTION);
+          for (firewall_connection_t fw_connection = FIREWALL_OR_CONNECTION;
+               fw_connection <= FIREWALL_DIR_CONNECTION; fw_connection++) {
+            for (int pref_only = 0; pref_only <= 1; pref_only++) {
+
+              /* Ignoring all other settings, want_a should choose the address
+               * for bridge clients */
+              tt_assert(fascist_firewall_choose_address(&ipv4_or_ap,
+                                                             &ipv6_or_ap, 1,
+                                                             fw_connection,
+                                                             pref_only)
+                        == &ipv4_or_ap);
+              tt_assert(fascist_firewall_choose_address(&ipv4_or_ap,
+                                                             &ipv6_or_ap, 0,
+                                                             fw_connection,
+                                                             pref_only)
+                        == &ipv6_or_ap);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  /* Choose an address with IPv4 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 0;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv4_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv4_dir_ap);
+
+  /* Choose an address with IPv6 on */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 0;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv6_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == &ipv6_dir_ap);
+
+  /* Choose an address with everything off */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 0;
+  mock_options.ClientUseIPv6 = 0;
+  mock_options.UseBridges = 0;
+
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == NULL);
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 1)
+            == NULL);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == NULL);
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 1)
+            == NULL);
+
+  /* Choose from unusual inputs */
+  memset(&mock_options, 0, sizeof(or_options_t));
+  mock_options.ClientUseIPv4 = 1;
+  mock_options.ClientUseIPv6 = 1;
+  mock_options.UseBridges = 1;
+
+  tt_assert(fascist_firewall_choose_address(&ipv4_or_ap, &n_ipv6_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv4_or_ap);
+  tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &ipv6_or_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == &ipv6_or_ap);
+  tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0,
+                                                 FIREWALL_OR_CONNECTION, 0)
+            == NULL);
+
+  tt_assert(fascist_firewall_choose_address(&ipv4_dir_ap, &n_ipv6_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv4_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &ipv6_dir_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == &ipv6_dir_ap);
+  tt_assert(fascist_firewall_choose_address(&n_ipv4_ap, &n_ipv6_ap, 0,
+                                                 FIREWALL_DIR_CONNECTION, 0)
+            == NULL);
+
+ done:
+  UNMOCK(get_options);
+}
+
+#undef TEST_IPV4_ADDR_STR
+#undef TEST_IPV6_ADDR_STR
+#undef TEST_IPV4_OR_PORT
+#undef TEST_IPV4_DIR_PORT
+#undef TEST_IPV6_OR_PORT
+#undef TEST_IPV6_DIR_PORT
+
 struct testcase_t policy_tests[] = {
 struct testcase_t policy_tests[] = {
   { "router_dump_exit_policy_to_string", test_dump_exit_policy_to_string, 0,
   { "router_dump_exit_policy_to_string", test_dump_exit_policy_to_string, 0,
     NULL, NULL },
     NULL, NULL },
@@ -1137,6 +1580,10 @@ struct testcase_t policy_tests[] = {
   { "reject_interface_address", test_policies_reject_interface_address, 0,
   { "reject_interface_address", test_policies_reject_interface_address, 0,
     NULL, NULL },
     NULL, NULL },
   { "reject_port_address", test_policies_reject_port_address, 0, NULL, NULL },
   { "reject_port_address", test_policies_reject_port_address, 0, NULL, NULL },
+  { "fascist_firewall_allows_address",
+    test_policies_fascist_firewall_allows_address, 0, NULL, NULL },
+  { "fascist_firewall_choose_address",
+    test_policies_fascist_firewall_choose_address, 0, NULL, NULL },
   END_OF_TESTCASES
   END_OF_TESTCASES
 };
 };