Selaa lähdekoodia

Merge branch 'getinfo-private-exitpolicy-v4-squashed'

Nick Mathewson 8 vuotta sitten
vanhempi
commit
35e886fe13

+ 7 - 0
changes/bug17027-reject-private-bind-port

@@ -0,0 +1,7 @@
+  o Minor bug fixes (security, exit policies):
+    - ExitPolicyRejectPrivate rejects more private addresses by default:
+      * the relay's outbound bind addresses (if configured), and
+      * the relay's configured port addresses (such as ORPort and DirPort).
+      Resolves ticket 17027. Patch by "teor".
+      Patch on 42b8fb5a1523 (11 Nov 2007), released in 0.2.0.11-alpha,
+      and on 0.2.7.3-rc.

+ 6 - 0
changes/getinfo-private-exitpolicy

@@ -0,0 +1,6 @@
+  o Minor features (exit policies, controllers):
+    - Add controller getinfo exit-policy/reject-private/[default,relay]
+      for the reject rules added by ExitPolicyRejectPrivate. This makes
+      it easier for stem to display exit policies.
+    - Add unit tests for getinfo exit-policy/*.
+      Completes ticket #17183. Patch by "teor".

+ 8 - 5
doc/tor.1.txt

@@ -1571,7 +1571,7 @@ is non-zero):
     used with accept6/reject6.) +
  +
     Private addresses are rejected by default (at the beginning of your exit
-    policy), along with the configured primary public IPv4 and IPv6 addresses,
+    policy), along with any configured primary public IPv4 and IPv6 addresses,
     and any public IPv4 and IPv6 addresses on any interface on the relay.
     These private addresses are rejected unless you set the
     ExitPolicyRejectPrivate config option to 0. For example, once you've done
@@ -1609,10 +1609,13 @@ is non-zero):
     IPv4 and IPv6 addresses.
 
 [[ExitPolicyRejectPrivate]] **ExitPolicyRejectPrivate** **0**|**1**::
-    Reject all private (local) networks, along with your own configured public
-    IPv4 and IPv6 addresses, at the beginning of your exit policy. Also reject
-    any public IPv4 and IPv6 addresses on any interface on the relay. (If
-    IPv6Exit is not set, all IPv6 addresses will be rejected anyway.)
+    Reject all private (local) networks, along with any configured public
+    IPv4 and IPv6 addresses, at the beginning of your exit policy. (This
+    includes the IPv4 and IPv6 addresses advertised by the relay, any
+    OutboundBindAddress, and the bind addresses of any port options, such as
+    ORPort and DirPort.) This also rejects any public IPv4 and IPv6 addresses
+    on any interface on the relay. (If IPv6Exit is not set, all IPv6 addresses
+    will be rejected anyway.)
     See above entry on ExitPolicy.
     (Default: 1)
 

+ 4 - 5
src/or/config.c

@@ -562,7 +562,6 @@ static char *get_bindaddr_from_transport_listen_line(const char *line,
 static int parse_dir_authority_line(const char *line,
                                  dirinfo_type_t required_type,
                                  int validate_only);
-static void port_cfg_free(port_cfg_t *port);
 static int parse_ports(or_options_t *options, int validate_only,
                               char **msg_out, int *n_ports_out,
                               int *world_writable_control_socket);
@@ -5737,7 +5736,7 @@ parse_dir_fallback_line(const char *line,
 }
 
 /** Allocate and return a new port_cfg_t with reasonable defaults. */
-static port_cfg_t *
+STATIC port_cfg_t *
 port_cfg_new(size_t namelen)
 {
   tor_assert(namelen <= SIZE_T_CEILING - sizeof(port_cfg_t) - 1);
@@ -5749,7 +5748,7 @@ port_cfg_new(size_t namelen)
 }
 
 /** Free all storage held in <b>port</b> */
-static void
+STATIC void
 port_cfg_free(port_cfg_t *port)
 {
   tor_free(port);
@@ -6673,8 +6672,8 @@ check_server_ports(const smartlist_t *ports,
 
 /** Return a list of port_cfg_t for client ports parsed from the
  * options. */
-const smartlist_t *
-get_configured_ports(void)
+MOCK_IMPL(const smartlist_t *,
+get_configured_ports,(void))
 {
   if (!configured_ports)
     configured_ports = smartlist_new();

+ 3 - 1
src/or/config.h

@@ -76,7 +76,7 @@ int write_to_data_subdir(const char* subdir, const char* fname,
 
 int get_num_cpus(const or_options_t *options);
 
-const smartlist_t *get_configured_ports(void);
+MOCK_DECL(const smartlist_t *,get_configured_ports,(void));
 int get_first_advertised_port_by_type_af(int listener_type,
                                          int address_family);
 #define get_primary_or_port() \
@@ -140,6 +140,8 @@ smartlist_t *get_options_for_server_transport(const char *transport);
 extern struct config_format_t options_format;
 #endif
 
+STATIC port_cfg_t *port_cfg_new(size_t namelen);
+STATIC void port_cfg_free(port_cfg_t *port);
 STATIC void or_options_free(or_options_t *options);
 STATIC int options_validate(or_options_t *old_options,
                             or_options_t *options,

+ 6 - 0
src/or/control.c

@@ -2562,6 +2562,12 @@ static const getinfo_item_t getinfo_items[] = {
        "v3 Networkstatus consensus as retrieved from a DirPort."),
   ITEM("exit-policy/default", policies,
        "The default value appended to the configured exit policy."),
+  ITEM("exit-policy/reject-private/default", policies,
+       "The default rules appended to the configured exit policy by"
+       " ExitPolicyRejectPrivate."),
+  ITEM("exit-policy/reject-private/relay", policies,
+       "The relay-specific rules appended to the configured exit policy by"
+       " ExitPolicyRejectPrivate."),
   ITEM("exit-policy/full", policies, "The entire exit policy of onion router"),
   ITEM("exit-policy/ipv4", policies, "IPv4 parts of exit policy"),
   ITEM("exit-policy/ipv6", policies, "IPv6 parts of exit policy"),

+ 323 - 106
src/or/policies.c

@@ -62,14 +62,15 @@ static const char *private_nets[] = {
   NULL
 };
 
-static int policies_parse_exit_policy_internal(config_line_t *cfg,
-                                               smartlist_t **dest,
-                                               int ipv6_exit,
-                                               int rejectprivate,
-                                               uint32_t local_address,
-                                               tor_addr_t *ipv6_local_address,
-                                               int reject_interface_addresses,
-                                               int add_default_policy);
+static int policies_parse_exit_policy_internal(
+                                      config_line_t *cfg,
+                                      smartlist_t **dest,
+                                      int ipv6_exit,
+                                      int rejectprivate,
+                                      const smartlist_t *configured_addresses,
+                                      int reject_interface_addresses,
+                                      int reject_configured_port_addresses,
+                                      int add_default_policy);
 
 /** Replace all "private" entries in *<b>policy</b> with their expanded
  * equivalents. */
@@ -443,7 +444,7 @@ validate_addr_policies(const or_options_t *options, char **msg)
   smartlist_t *addr_policy=NULL;
   *msg = NULL;
 
-  if (policies_parse_exit_policy_from_options(options,0,NULL,0,&addr_policy)) {
+  if (policies_parse_exit_policy_from_options(options,0,NULL,&addr_policy)) {
     REJECT("Error in ExitPolicy entry.");
   }
 
@@ -864,7 +865,7 @@ addr_policy_intersects(addr_policy_t *a, addr_policy_t *b)
 
 /** Add the exit policy described by <b>more</b> to <b>policy</b>.
  */
-static void
+STATIC void
 append_exit_policy_string(smartlist_t **policy, const char *more)
 {
   config_line_t tmp;
@@ -881,6 +882,9 @@ append_exit_policy_string(smartlist_t **policy, const char *more)
 void
 addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr)
 {
+  tor_assert(dest);
+  tor_assert(addr);
+
   addr_policy_t p, *add;
   memset(&p, 0, sizeof(p));
   p.policy_type = ADDR_POLICY_REJECT;
@@ -893,6 +897,71 @@ addr_policy_append_reject_addr(smartlist_t **dest, const tor_addr_t *addr)
   if (!*dest)
     *dest = smartlist_new();
   smartlist_add(*dest, add);
+  log_debug(LD_CONFIG, "Adding a reject ExitPolicy 'reject %s:*'",
+            fmt_addr(addr));
+
+}
+
+/* Is addr public for the purposes of rejection? */
+static int
+tor_addr_is_public_for_reject(const tor_addr_t *addr)
+{
+  return !tor_addr_is_null(addr) && !tor_addr_is_internal(addr, 0);
+}
+
+/* Add "reject <b>addr</b>:*" to <b>dest</b>, creating the list as needed.
+ * Filter the address, only adding an IPv4 reject rule if ipv4_rules
+ * is true, and similarly for ipv6_rules. Check each address returns true for
+ * tor_addr_is_public_for_reject before adding it.
+ */
+static void
+addr_policy_append_reject_addr_filter(smartlist_t **dest,
+                                      const tor_addr_t *addr,
+                                      int ipv4_rules,
+                                      int ipv6_rules)
+{
+  tor_assert(dest);
+  tor_assert(addr);
+
+  /* Only reject IP addresses which are public */
+  if (tor_addr_is_public_for_reject(addr)) {
+
+    /* Reject IPv4 addresses and IPv6 addresses based on the filters */
+    int is_ipv4 = tor_addr_is_v4(addr);
+    if ((is_ipv4 && ipv4_rules) || (!is_ipv4 && ipv6_rules)) {
+      addr_policy_append_reject_addr(dest, addr);
+    }
+  }
+}
+
+/** Add "reject addr:*" to <b>dest</b>, for each addr in addrs, creating the
+  * list as needed. */
+void
+addr_policy_append_reject_addr_list(smartlist_t **dest,
+                                    const smartlist_t *addrs)
+{
+  tor_assert(dest);
+  tor_assert(addrs);
+
+  SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, addr) {
+    addr_policy_append_reject_addr(dest, addr);
+  } SMARTLIST_FOREACH_END(addr);
+}
+
+/** Add "reject addr:*" to <b>dest</b>, for each addr in addrs, creating the
+ * list as needed. Filter using */
+static void
+addr_policy_append_reject_addr_list_filter(smartlist_t **dest,
+                                           const smartlist_t *addrs,
+                                           int ipv4_rules,
+                                           int ipv6_rules)
+{
+  tor_assert(dest);
+  tor_assert(addrs);
+
+  SMARTLIST_FOREACH_BEGIN(addrs, tor_addr_t *, addr) {
+    addr_policy_append_reject_addr_filter(dest, addr, ipv4_rules, ipv6_rules);
+  } SMARTLIST_FOREACH_END(addr);
 }
 
 /** Detect and excise "dead code" from the policy *<b>dest</b>. */
@@ -979,6 +1048,76 @@ exit_policy_remove_redundancies(smartlist_t *dest)
   }
 }
 
+/** Reject private helper for policies_parse_exit_policy_internal: rejects
+ * publicly routable addresses on this exit relay.
+ *
+ * Add reject entries to the linked list *dest:
+ *   - if configured_addresses is non-NULL, add entries that reject each
+ *     tor_addr_t* in the list as a destination.
+ *   - if reject_interface_addresses is true, add entries that reject each
+ *     public IPv4 and IPv6 address of each interface on this machine.
+ *   - if reject_configured_port_addresses is true, add entries that reject
+ *     each IPv4 and IPv6 address configured for a port.
+ *
+ * IPv6 entries are only added if ipv6_exit is true. (All IPv6 addresses are
+ * already blocked by policies_parse_exit_policy_internal if ipv6_exit is
+ * false.)
+ *
+ * The list *dest is created as needed.
+ */
+void
+policies_parse_exit_policy_reject_private(
+                                      smartlist_t **dest,
+                                      int ipv6_exit,
+                                      const smartlist_t *configured_addresses,
+                                      int reject_interface_addresses,
+                                      int reject_configured_port_addresses)
+{
+  tor_assert(dest);
+
+  /* Reject configured addresses, if they are from public netblocks. */
+  if (configured_addresses) {
+    addr_policy_append_reject_addr_list_filter(dest, configured_addresses,
+                                               1, ipv6_exit);
+  }
+
+  /* Reject configured port addresses, if they are from public netblocks. */
+  if (reject_configured_port_addresses) {
+    const smartlist_t *port_addrs = get_configured_ports();
+
+    SMARTLIST_FOREACH_BEGIN(port_addrs, port_cfg_t *, port) {
+
+      /* Only reject port IP addresses, not port unix sockets */
+      if (!port->is_unix_addr) {
+        addr_policy_append_reject_addr_filter(dest, &port->addr, 1, ipv6_exit);
+      }
+    } SMARTLIST_FOREACH_END(port);
+  }
+
+  /* Reject local addresses from public netblocks on any interface. */
+  if (reject_interface_addresses) {
+    smartlist_t *public_addresses = NULL;
+
+    /* Reject public IPv4 addresses on any interface */
+    public_addresses = get_interface_address6_list(LOG_INFO, AF_INET, 0);
+    addr_policy_append_reject_addr_list_filter(dest, public_addresses, 1, 0);
+    free_interface_address6_list(public_addresses);
+
+    /* Don't look for IPv6 addresses if we're configured as IPv4-only */
+    if (ipv6_exit) {
+      /* Reject public IPv6 addresses on any interface */
+      public_addresses = get_interface_address6_list(LOG_INFO, AF_INET6, 0);
+      addr_policy_append_reject_addr_list_filter(dest, public_addresses, 0, 1);
+      free_interface_address6_list(public_addresses);
+    }
+  }
+
+  /* If addresses were added multiple times, remove all but one of them. */
+  if (*dest) {
+    exit_policy_remove_redundancies(*dest);
+  }
+}
+
 #define DEFAULT_EXIT_POLICY                                         \
   "reject *:25,reject *:119,reject *:135-139,reject *:445,"         \
   "reject *:563,reject *:1214,reject *:4661-4666,"                  \
@@ -986,16 +1125,12 @@ exit_policy_remove_redundancies(smartlist_t *dest)
 
 /** Parse the exit policy <b>cfg</b> into the linked list *<b>dest</b>.
  *
- * If <b>ipv6_exit</b> is true, prepend "reject *6:*" to the policy.
+ * If <b>ipv6_exit</b> is false, prepend "reject *6:*" to the policy.
  *
  * If <b>rejectprivate</b> is true:
  *   - prepend "reject private:*" to the policy.
- *   - if local_address is non-zero, treat it as a host-order IPv4 address,
- *     and prepend an entry that rejects it as a destination.
- *   - if ipv6_local_address is non-NULL, prepend an entry that rejects it as
- *     a destination.
- *   - if reject_interface_addresses is true, prepend entries that reject each
- *     public IPv4 and IPv6 address of each interface on this machine.
+ *   - prepend entries that reject publicly routable addresses on this exit
+ *     relay by calling policies_parse_exit_policy_reject_private
  *
  * If cfg doesn't end in an absolute accept or reject and if
  * <b>add_default_policy</b> is true, add the default exit
@@ -1008,12 +1143,13 @@ exit_policy_remove_redundancies(smartlist_t *dest)
  * see router_add_exit_policy.
  */
 static int
-policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest,
+policies_parse_exit_policy_internal(config_line_t *cfg,
+                                    smartlist_t **dest,
                                     int ipv6_exit,
                                     int rejectprivate,
-                                    uint32_t local_address,
-                                    tor_addr_t *ipv6_local_address,
+                                    const smartlist_t *configured_addresses,
                                     int reject_interface_addresses,
+                                    int reject_configured_port_addresses,
                                     int add_default_policy)
 {
   if (!ipv6_exit) {
@@ -1022,69 +1158,12 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest,
   if (rejectprivate) {
     /* Reject IPv4 and IPv6 reserved private netblocks */
     append_exit_policy_string(dest, "reject private:*");
-    /* Reject our local IPv4 address */
-    if (local_address) {
-      char buf[POLICY_BUF_LEN];
-      tor_snprintf(buf, sizeof(buf), "reject %s:*", fmt_addr32(local_address));
-      append_exit_policy_string(dest, buf);
-      log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for our published "
-               "IPv4 address", buf);
-    }
-    /* Reject our local IPv6 address */
-    if (ipv6_exit && ipv6_local_address != NULL) {
-      if (tor_addr_is_v4(ipv6_local_address)) {
-        log_warn(LD_CONFIG, "IPv4 address '%s' provided as our IPv6 local "
-                 "address", fmt_addr(ipv6_local_address));
-      } else {
-        char buf6[POLICY_BUF_LEN];
-        tor_snprintf(buf6, sizeof(buf6), "reject [%s]:*",
-                     fmt_addr(ipv6_local_address));
-        append_exit_policy_string(dest, buf6);
-        log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for our "
-                 "published IPv6 address", buf6);
-      }
-    }
-    /* Reject local addresses from public netblocks on any interface,
-     * but don't reject our published addresses twice */
-    if (reject_interface_addresses) {
-      smartlist_t *public_addresses = NULL;
-      char bufif[POLICY_BUF_LEN];
-
-      /* Reject public IPv4 addresses on any interface,
-       * but don't reject our published IPv4 address twice */
-      public_addresses = get_interface_address6_list(LOG_INFO, AF_INET, 0);
-      SMARTLIST_FOREACH_BEGIN(public_addresses, tor_addr_t *, a) {
-        if (!tor_addr_eq_ipv4h(a, local_address)) {
-          tor_snprintf(bufif, sizeof(bufif), "reject %s:*",
-                       fmt_addr(a));
-          append_exit_policy_string(dest, bufif);
-          log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for a local "
-                   "interface's public IPv4 address", bufif);
-        }
-      } SMARTLIST_FOREACH_END(a);
-      free_interface_address6_list(public_addresses);
-
-      if (ipv6_exit) {
-        /* Reject public IPv6 addresses on any interface,
-         * but don't reject our published IPv6 address (if any) twice */
-        public_addresses = get_interface_address6_list(LOG_INFO, AF_INET6, 0);
-        SMARTLIST_FOREACH_BEGIN(public_addresses, tor_addr_t *, a) {
-          /* if we don't have an IPv6 local address, we won't have rejected
-           * it above. This could happen if a future release does IPv6
-           * autodiscovery, and we are waiting to discover our external IPv6
-           * address */
-          if (ipv6_local_address == NULL
-              || !tor_addr_eq(ipv6_local_address, a)) {
-            tor_snprintf(bufif, sizeof(bufif), "reject6 [%s]:*",
-                         fmt_addr(a));
-            append_exit_policy_string(dest, bufif);
-            log_info(LD_CONFIG, "Adding a reject ExitPolicy '%s' for a local "
-                     "interface's public IPv6 address", bufif);
-          }
-        } SMARTLIST_FOREACH_END(a);
-        free_interface_address6_list(public_addresses);
-      }
-    }
+    /* Reject IPv4 and IPv6 publicly routable addresses on this exit relay */
+    policies_parse_exit_policy_reject_private(
+                                            dest, ipv6_exit,
+                                            configured_addresses,
+                                            reject_interface_addresses,
+                                            reject_configured_port_addresses);
   }
   if (parse_addr_policy(cfg, dest, -1))
     return -1;
@@ -1167,12 +1246,8 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest,
  * If <b>EXIT_POLICY_REJECT_PRIVATE</b> bit is set in <b>options</b>:
  *   - prepend an entry that rejects all destinations in all netblocks
  *     reserved for private use.
- *   - if local_address is non-zero, treat it as a host-order IPv4 address,
- *     and prepend an entry that rejects it as a destination.
- *   - if ipv6_local_address is non-NULL, prepend an entry that rejects it as
- *     a destination.
- *   - if reject_interface_addresses is true, prepend entries that reject each
- *     public IPv4 and IPv6 address of each interface on this machine.
+ *   - prepend entries that reject publicly routable addresses on this exit
+ *     relay by calling policies_parse_exit_policy_internal
  *
  * If <b>EXIT_POLICY_ADD_DEFAULT</b> bit is set in <b>options</b>, append
  * default exit policy entries to <b>result</b> smartlist.
@@ -1180,9 +1255,7 @@ policies_parse_exit_policy_internal(config_line_t *cfg, smartlist_t **dest,
 int
 policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
                            exit_policy_parser_cfg_t options,
-                           uint32_t local_address,
-                           tor_addr_t *ipv6_local_address,
-                           int reject_interface_addresses)
+                           const smartlist_t *configured_addresses)
 {
   int ipv6_enabled = (options & EXIT_POLICY_IPV6_ENABLED) ? 1 : 0;
   int reject_private = (options & EXIT_POLICY_REJECT_PRIVATE) ? 1 : 0;
@@ -1190,12 +1263,51 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
 
   return policies_parse_exit_policy_internal(cfg,dest,ipv6_enabled,
                                              reject_private,
-                                             local_address,
-                                             ipv6_local_address,
-                                             reject_interface_addresses,
+                                             configured_addresses,
+                                             reject_private,
+                                             reject_private,
                                              add_default);
 }
 
+/** Helper function that adds addr to a smartlist as long as it is non-NULL
+ * and not tor_addr_is_null(). */
+static void
+policies_add_addr_to_smartlist(smartlist_t *addr_list, const tor_addr_t *addr)
+{
+  if (addr && !tor_addr_is_null(addr)) {
+    smartlist_add(addr_list, (void *)addr);
+  }
+}
+
+/** Helper function that adds ipv4h_addr to a smartlist as a tor_addr_t *,
+ * by converting it to a tor_addr_t and passing it to
+ * policies_add_addr_to_smartlist. */
+static void
+policies_add_ipv4h_to_smartlist(smartlist_t *addr_list, uint32_t ipv4h_addr)
+{
+  if (ipv4h_addr) {
+    tor_addr_t ipv4_tor_addr;
+    tor_addr_from_ipv4h(&ipv4_tor_addr, ipv4h_addr);
+    policies_add_addr_to_smartlist(addr_list, (void *)&ipv4_tor_addr);
+  }
+}
+
+/** Helper function that adds or_options->OutboundBindAddressIPv[4|6]_ to a
+ * smartlist as a tor_addr_t *, as long as or_options is non-NULL,
+ * by passing them to policies_add_addr_to_smartlist. */
+static void
+policies_add_outbound_addresses_to_smartlist(smartlist_t *addr_list,
+                                             const or_options_t *or_options)
+{
+  if (or_options) {
+    policies_add_addr_to_smartlist(addr_list,
+                                   &or_options->OutboundBindAddressIPv4_);
+    policies_add_addr_to_smartlist(addr_list,
+                                   &or_options->OutboundBindAddressIPv6_);
+  }
+}
+
+
 /** Parse <b>ExitPolicy</b> member of <b>or_options</b> into <b>result</b>
  * smartlist.
  * If <b>or_options->IPv6Exit</b> is false, prepend an entry that
@@ -1205,11 +1317,13 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
  *  - prepend an entry that rejects all destinations in all netblocks reserved
  *    for private use.
  *  - if local_address is non-zero, treat it as a host-order IPv4 address, and
- *    prepend an entry that rejects it as a destination.
- *  - if ipv6_local_address is non-NULL, prepend an entry that rejects it as a
- *    destination.
- *  - if reject_interface_addresses is true, prepend entries that reject each
- *    public IPv4 and IPv6 address of each interface on this machine.
+ *    add it to the list of configured addresses.
+ *  - if ipv6_local_address is non-NULL, and not the null tor_addr_t, add it
+ *    to the list of configured addresses.
+ *  - if or_options->OutboundBindAddressIPv4_ is not the null tor_addr_t, add
+ *    it to the list of configured addresses.
+ *  - if or_options->OutboundBindAddressIPv6_ is not the null tor_addr_t, add
+ *    it to the list of configured addresses.
  *
  * If <b>or_options->BridgeRelay</b> is false, append entries of default
  * Tor exit policy into <b>result</b> smartlist.
@@ -1220,18 +1334,21 @@ policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
 int
 policies_parse_exit_policy_from_options(const or_options_t *or_options,
                                         uint32_t local_address,
-                                        tor_addr_t *ipv6_local_address,
-                                        int reject_interface_addresses,
+                                        const tor_addr_t *ipv6_local_address,
                                         smartlist_t **result)
 {
   exit_policy_parser_cfg_t parser_cfg = 0;
+  smartlist_t *configured_addresses = smartlist_new();
+  int rv = 0;
 
+  /* Short-circuit for non-exit relays */
   if (or_options->ExitRelay == 0) {
     append_exit_policy_string(result, "reject *4:*");
     append_exit_policy_string(result, "reject *6:*");
     return 0;
   }
 
+  /* Configure the parser */
   if (or_options->IPv6Exit) {
     parser_cfg |= EXIT_POLICY_IPV6_ENABLED;
   }
@@ -1244,10 +1361,20 @@ policies_parse_exit_policy_from_options(const or_options_t *or_options,
     parser_cfg |= EXIT_POLICY_ADD_DEFAULT;
   }
 
-  return policies_parse_exit_policy(or_options->ExitPolicy,result,
-                                    parser_cfg,local_address,
-                                    ipv6_local_address,
-                                    reject_interface_addresses);
+  /* Add the configured addresses to the tor_addr_t* list */
+  policies_add_ipv4h_to_smartlist(configured_addresses, local_address);
+  policies_add_addr_to_smartlist(configured_addresses, ipv6_local_address);
+  policies_add_outbound_addresses_to_smartlist(configured_addresses,
+                                               or_options);
+
+  rv = policies_parse_exit_policy(or_options->ExitPolicy, result, parser_cfg,
+                                  configured_addresses);
+
+  /* We don't need to free the pointers in this list, they are either constant
+   * or locally scoped. */
+  smartlist_free(configured_addresses);
+
+  return rv;
 }
 
 /** Add "reject *:*" to the end of the policy in *<b>dest</b>, allocating
@@ -1934,6 +2061,53 @@ compare_tor_addr_to_node_policy(const tor_addr_t *addr, uint16_t port,
   }
 }
 
+/**
+ * Given <b>policy_list</b>, a list of addr_policy_t, produce a string
+ * representation of the list.
+ * If <b>include_ipv4</b> is true, include IPv4 entries.
+ * If <b>include_ipv6</b> is true, include IPv6 entries.
+ */
+char *
+policy_dump_to_string(const smartlist_t *policy_list,
+                      int include_ipv4,
+                      int include_ipv6)
+{
+  smartlist_t *policy_string_list;
+  char *policy_string = NULL;
+
+  policy_string_list = smartlist_new();
+
+  SMARTLIST_FOREACH_BEGIN(policy_list, addr_policy_t *, tmpe) {
+    char *pbuf;
+    int bytes_written_to_pbuf;
+    if ((tor_addr_family(&tmpe->addr) == AF_INET6) && (!include_ipv6)) {
+      continue; /* Don't include IPv6 parts of address policy */
+    }
+    if ((tor_addr_family(&tmpe->addr) == AF_INET) && (!include_ipv4)) {
+      continue; /* Don't include IPv4 parts of address policy */
+    }
+
+    pbuf = tor_malloc(POLICY_BUF_LEN);
+    bytes_written_to_pbuf = policy_write_item(pbuf,POLICY_BUF_LEN, tmpe, 1);
+
+    if (bytes_written_to_pbuf < 0) {
+      log_warn(LD_BUG, "policy_dump_to_string ran out of room!");
+      tor_free(pbuf);
+      goto done;
+    }
+
+    smartlist_add(policy_string_list,pbuf);
+  } SMARTLIST_FOREACH_END(tmpe);
+
+  policy_string = smartlist_join_strings(policy_string_list, "\n", 0, NULL);
+
+done:
+  SMARTLIST_FOREACH(policy_string_list, char *, str, tor_free(str));
+  smartlist_free(policy_string_list);
+
+  return policy_string;
+}
+
 /** Implementation for GETINFO control command: knows the answer for questions
  * about "exit-policy/..." */
 int
@@ -1945,6 +2119,49 @@ getinfo_helper_policies(control_connection_t *conn,
   (void) errmsg;
   if (!strcmp(question, "exit-policy/default")) {
     *answer = tor_strdup(DEFAULT_EXIT_POLICY);
+  } else if (!strcmp(question, "exit-policy/reject-private/default")) {
+    smartlist_t *private_policy_strings;
+    const char **priv = private_nets;
+
+    private_policy_strings = smartlist_new();
+
+    while (*priv != NULL) {
+      /* IPv6 addresses are in "[]" and contain ":",
+       * IPv4 addresses are not in "[]" and contain "." */
+      smartlist_add_asprintf(private_policy_strings, "reject %s:*", *priv);
+      priv++;
+    }
+
+    *answer = smartlist_join_strings(private_policy_strings,
+                                     ",", 0, NULL);
+
+    SMARTLIST_FOREACH(private_policy_strings, char *, str, tor_free(str));
+    smartlist_free(private_policy_strings);
+  } else if (!strcmp(question, "exit-policy/reject-private/relay")) {
+    const or_options_t *options = get_options();
+    const routerinfo_t *me = router_get_my_routerinfo();
+    smartlist_t *private_policy_list = smartlist_new();
+    smartlist_t *configured_addresses = smartlist_new();
+
+    if (!me) {
+      *errmsg = "router_get_my_routerinfo returned NULL";
+      return -1;
+    }
+
+    /* Add the configured addresses to the tor_addr_t* list */
+    policies_add_ipv4h_to_smartlist(configured_addresses, me->addr);
+    policies_add_addr_to_smartlist(configured_addresses, &me->ipv6_addr);
+    policies_add_outbound_addresses_to_smartlist(configured_addresses,
+                                                 options);
+
+    policies_parse_exit_policy_reject_private(
+                                            &private_policy_list,
+                                            options->IPv6Exit,
+                                            configured_addresses,
+                                            1, 1);
+    *answer = policy_dump_to_string(private_policy_list, 1, 1);
+
+    addr_policy_list_free(private_policy_list);
   } else if (!strcmpstart(question, "exit-policy/")) {
     const routerinfo_t *me = router_get_my_routerinfo();
 

+ 21 - 9
src/or/policies.h

@@ -44,26 +44,34 @@ addr_policy_t *addr_policy_get_canonical_entry(addr_policy_t *ent);
 int cmp_addr_policies(smartlist_t *a, smartlist_t *b);
 MOCK_DECL(addr_policy_result_t, compare_tor_addr_to_addr_policy,
     (const tor_addr_t *addr, uint16_t port, const smartlist_t *policy));
-
 addr_policy_result_t compare_tor_addr_to_node_policy(const tor_addr_t *addr,
                               uint16_t port, const node_t *node);
 
-int policies_parse_exit_policy_from_options(const or_options_t *or_options,
-                                            uint32_t local_address,
-                                            tor_addr_t *ipv6_local_address,
-                                            int reject_interface_addresses,
-                                            smartlist_t **result);
+int policies_parse_exit_policy_from_options(
+                                          const or_options_t *or_options,
+                                          uint32_t local_address,
+                                          const tor_addr_t *ipv6_local_address,
+                                          smartlist_t **result);
 int policies_parse_exit_policy(config_line_t *cfg, smartlist_t **dest,
                                exit_policy_parser_cfg_t options,
-                               uint32_t local_address,
-                               tor_addr_t *ipv6_local_address,
-                               int reject_interface_addresses);
+                               const smartlist_t *configured_addresses);
+void policies_parse_exit_policy_reject_private(
+                                      smartlist_t **dest,
+                                      int ipv6_exit,
+                                      const smartlist_t *configured_addresses,
+                                      int reject_interface_addresses,
+                                      int reject_configured_port_addresses);
 void policies_exit_policy_append_reject_star(smartlist_t **dest);
 void addr_policy_append_reject_addr(smartlist_t **dest,
                                     const tor_addr_t *addr);
+void addr_policy_append_reject_addr_list(smartlist_t **dest,
+                                         const smartlist_t *addrs);
 void policies_set_node_exitpolicy_to_reject_all(node_t *exitrouter);
 int exit_policy_is_general_exit(smartlist_t *policy);
 int policy_is_reject_star(const smartlist_t *policy, sa_family_t family);
+char * policy_dump_to_string(const smartlist_t *policy_list,
+                             int include_ipv4,
+                             int include_ipv6);
 int getinfo_helper_policies(control_connection_t *conn,
                             const char *question, char **answer,
                             const char **errmsg);
@@ -84,5 +92,9 @@ addr_policy_result_t compare_tor_addr_to_short_policy(
                           const tor_addr_t *addr, uint16_t port,
                           const short_policy_t *policy);
 
+#ifdef POLICIES_PRIVATE
+void append_exit_policy_string(smartlist_t **policy, const char *more);
+#endif
+
 #endif
 

+ 4 - 35
src/or/router.c

@@ -1922,7 +1922,7 @@ router_build_fresh_descriptor(routerinfo_t **r, extrainfo_t **e)
     /* DNS is screwed up; don't claim to be an exit. */
     policies_exit_policy_append_reject_star(&ri->exit_policy);
   } else {
-    policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr,1,
+    policies_parse_exit_policy_from_options(options,ri->addr,&ri->ipv6_addr,
                                             &ri->exit_policy);
   }
   ri->policy_is_reject_star =
@@ -2728,44 +2728,13 @@ router_dump_exit_policy_to_string(const routerinfo_t *router,
                                   int include_ipv4,
                                   int include_ipv6)
 {
-  smartlist_t *exit_policy_strings;
-  char *policy_string = NULL;
-
   if ((!router->exit_policy) || (router->policy_is_reject_star)) {
     return tor_strdup("reject *:*");
   }
 
-  exit_policy_strings = smartlist_new();
-
-  SMARTLIST_FOREACH_BEGIN(router->exit_policy, addr_policy_t *, tmpe) {
-    char *pbuf;
-    int bytes_written_to_pbuf;
-    if ((tor_addr_family(&tmpe->addr) == AF_INET6) && (!include_ipv6)) {
-      continue; /* Don't include IPv6 parts of address policy */
-    }
-    if ((tor_addr_family(&tmpe->addr) == AF_INET) && (!include_ipv4)) {
-      continue; /* Don't include IPv4 parts of address policy */
-    }
-
-    pbuf = tor_malloc(POLICY_BUF_LEN);
-    bytes_written_to_pbuf = policy_write_item(pbuf,POLICY_BUF_LEN, tmpe, 1);
-
-    if (bytes_written_to_pbuf < 0) {
-      log_warn(LD_BUG, "router_dump_exit_policy_to_string ran out of room!");
-      tor_free(pbuf);
-      goto done;
-    }
-
-    smartlist_add(exit_policy_strings,pbuf);
-  } SMARTLIST_FOREACH_END(tmpe);
-
-  policy_string = smartlist_join_strings(exit_policy_strings, "\n", 0, NULL);
-
- done:
-  SMARTLIST_FOREACH(exit_policy_strings, char *, str, tor_free(str));
-  smartlist_free(exit_policy_strings);
-
-  return policy_string;
+  return policy_dump_to_string(router->exit_policy,
+                               include_ipv4,
+                               include_ipv6);
 }
 
 /** Copy the primary (IPv4) OR port (IP address and TCP port) for

+ 398 - 12
src/test/test_policy.c

@@ -2,8 +2,11 @@
 /* See LICENSE for licensing information */
 
 #include "or.h"
+#define CONFIG_PRIVATE
+#include "config.h"
 #include "router.h"
 #include "routerparse.h"
+#define POLICIES_PRIVATE
 #include "policies.h"
 #include "test.h"
 
@@ -49,7 +52,7 @@ test_policy_summary_helper(const char *policy_str,
 
   r = policies_parse_exit_policy(&line, &policy,
                                  EXIT_POLICY_IPV6_ENABLED |
-                                 EXIT_POLICY_ADD_DEFAULT, 0, NULL, 0);
+                                 EXIT_POLICY_ADD_DEFAULT, NULL);
   tt_int_op(r,OP_EQ, 0);
 
   summary = policy_summarize(policy, AF_INET);
@@ -80,7 +83,8 @@ test_policies_general(void *arg)
               *policy7 = NULL, *policy8 = NULL, *policy9 = NULL,
               *policy10 = NULL, *policy11 = NULL, *policy12 = NULL;
   addr_policy_t *p;
-  tor_addr_t tar;
+  tor_addr_t tar, tar2;
+  smartlist_t *addr_list = NULL;
   config_line_t line;
   smartlist_t *sm = NULL;
   char *policy_str = NULL;
@@ -115,17 +119,22 @@ test_policies_general(void *arg)
   tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy2,
                                               EXIT_POLICY_IPV6_ENABLED |
                                               EXIT_POLICY_REJECT_PRIVATE |
-                                              EXIT_POLICY_ADD_DEFAULT, 0,
-                                              NULL, 0));
+                                              EXIT_POLICY_ADD_DEFAULT, NULL));
 
   tt_assert(policy2);
 
-  tor_addr_parse(&tar, "[2000::1234]");
+  tor_addr_from_ipv4h(&tar, 0x0306090cu);
+  tor_addr_parse(&tar2, "[2000::1234]");
+  addr_list = smartlist_new();
+  smartlist_add(addr_list, &tar);
+  smartlist_add(addr_list, &tar2);
   tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy12,
                                                  EXIT_POLICY_IPV6_ENABLED |
                                                  EXIT_POLICY_REJECT_PRIVATE |
                                                  EXIT_POLICY_ADD_DEFAULT,
-                                                 0x0306090cu, &tar, 1));
+                                                 addr_list));
+  smartlist_free(addr_list);
+  addr_list = NULL;
 
   tt_assert(policy12);
 
@@ -206,15 +215,15 @@ test_policies_general(void *arg)
   tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy8,
                                                  EXIT_POLICY_IPV6_ENABLED |
                                                  EXIT_POLICY_REJECT_PRIVATE |
-                                                 EXIT_POLICY_ADD_DEFAULT, 0,
-                                                 NULL, 0));
+                                                 EXIT_POLICY_ADD_DEFAULT,
+                                                 NULL));
 
   tt_assert(policy8);
 
   tt_int_op(0, OP_EQ, policies_parse_exit_policy(NULL, &policy9,
                                                  EXIT_POLICY_REJECT_PRIVATE |
-                                                 EXIT_POLICY_ADD_DEFAULT, 0,
-                                                 NULL, 0));
+                                                 EXIT_POLICY_ADD_DEFAULT,
+                                                 NULL));
 
   tt_assert(policy9);
 
@@ -268,8 +277,7 @@ test_policies_general(void *arg)
   line.next = NULL;
   tt_int_op(0, OP_EQ, policies_parse_exit_policy(&line,&policy,
                                               EXIT_POLICY_IPV6_ENABLED |
-                                              EXIT_POLICY_ADD_DEFAULT, 0,
-                                              NULL, 0));
+                                              EXIT_POLICY_ADD_DEFAULT, NULL));
   tt_assert(policy);
 
   //test_streq(policy->string, "accept *:80");
@@ -489,6 +497,245 @@ test_policies_general(void *arg)
   short_policy_free(short_parsed);
 }
 
+/** Helper: Check that policy_list contains address */
+static int
+test_policy_has_address_helper(const smartlist_t *policy_list,
+                               const tor_addr_t *addr)
+{
+  int found = 0;
+
+  tt_assert(policy_list);
+  tt_assert(addr);
+
+  SMARTLIST_FOREACH_BEGIN(policy_list, addr_policy_t*, p) {
+    if (tor_addr_eq(&p->addr, addr)) {
+      found = 1;
+    }
+  } SMARTLIST_FOREACH_END(p);
+
+  return found;
+
+ done:
+  return 0;
+}
+
+#define TEST_IPV4_ADDR (0x01020304)
+#define TEST_IPV6_ADDR ("2002::abcd")
+
+/** Run unit tests for rejecting the configured addresses on this exit relay
+ * using policies_parse_exit_policy_reject_private */
+static void
+test_policies_reject_exit_address(void *arg)
+{
+  smartlist_t *policy = NULL;
+  tor_addr_t ipv4_addr, ipv6_addr;
+  smartlist_t *ipv4_list, *ipv6_list, *both_list, *dupl_list;
+  (void)arg;
+
+  tor_addr_from_ipv4h(&ipv4_addr, TEST_IPV4_ADDR);
+  tor_addr_parse(&ipv6_addr, TEST_IPV6_ADDR);
+
+  ipv4_list = smartlist_new();
+  ipv6_list = smartlist_new();
+  both_list = smartlist_new();
+  dupl_list = smartlist_new();
+
+  smartlist_add(ipv4_list, &ipv4_addr);
+  smartlist_add(both_list, &ipv4_addr);
+  smartlist_add(dupl_list, &ipv4_addr);
+  smartlist_add(dupl_list, &ipv4_addr);
+  smartlist_add(dupl_list, &ipv4_addr);
+
+  smartlist_add(ipv6_list, &ipv6_addr);
+  smartlist_add(both_list, &ipv6_addr);
+  smartlist_add(dupl_list, &ipv6_addr);
+  smartlist_add(dupl_list, &ipv6_addr);
+
+  /* IPv4-Only Exits */
+
+  /* test that IPv4 addresses are rejected on an IPv4-only exit */
+  policies_parse_exit_policy_reject_private(&policy, 0, ipv4_list, 0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* test that IPv6 addresses are NOT rejected on an IPv4-only exit
+   * (all IPv6 addresses are rejected by policies_parse_exit_policy_internal
+   * on IPv4-only exits, so policies_parse_exit_policy_reject_private doesn't
+   * need to do anything) */
+  policies_parse_exit_policy_reject_private(&policy, 0, ipv6_list, 0, 0);
+  tt_assert(policy == NULL);
+
+  /* test that only IPv4 addresses are rejected on an IPv4-only exit */
+  policies_parse_exit_policy_reject_private(&policy, 0, both_list, 0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* Test that lists with duplicate entries produce the same results */
+  policies_parse_exit_policy_reject_private(&policy, 0, dupl_list, 0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* IPv4/IPv6 Exits */
+
+  /* test that IPv4 addresses are rejected on an IPv4/IPv6 exit */
+  policies_parse_exit_policy_reject_private(&policy, 1, ipv4_list, 0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* test that IPv6 addresses are rejected on an IPv4/IPv6 exit */
+  policies_parse_exit_policy_reject_private(&policy, 1, ipv6_list,  0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv6_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* test that IPv4 and IPv6 addresses are rejected on an IPv4/IPv6 exit */
+  policies_parse_exit_policy_reject_private(&policy, 1, both_list,  0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 2);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  tt_assert(test_policy_has_address_helper(policy, &ipv6_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* Test that lists with duplicate entries produce the same results */
+  policies_parse_exit_policy_reject_private(&policy, 1, dupl_list,  0, 0);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 2);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_addr));
+  tt_assert(test_policy_has_address_helper(policy, &ipv6_addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+ done:
+  addr_policy_list_free(policy);
+  smartlist_free(ipv4_list);
+  smartlist_free(ipv6_list);
+  smartlist_free(both_list);
+  smartlist_free(dupl_list);
+}
+
+static smartlist_t *test_configured_ports = NULL;
+const smartlist_t *mock_get_configured_ports(void);
+
+/** Returns test_configured_ports */
+const smartlist_t *
+mock_get_configured_ports(void)
+{
+  return test_configured_ports;
+}
+
+/** Run unit tests for rejecting publicly routable configured port addresses
+ * on this exit relay using policies_parse_exit_policy_reject_private */
+static void
+test_policies_reject_port_address(void *arg)
+{
+  smartlist_t *policy = NULL;
+  port_cfg_t *ipv4_port = NULL;
+  port_cfg_t *ipv6_port = NULL;
+  (void)arg;
+
+  test_configured_ports = smartlist_new();
+
+  ipv4_port = port_cfg_new(0);
+  tor_addr_from_ipv4h(&ipv4_port->addr, TEST_IPV4_ADDR);
+  smartlist_add(test_configured_ports, ipv4_port);
+
+  ipv6_port = port_cfg_new(0);
+  tor_addr_parse(&ipv6_port->addr, TEST_IPV6_ADDR);
+  smartlist_add(test_configured_ports, ipv6_port);
+
+  MOCK(get_configured_ports, mock_get_configured_ports);
+
+  /* test that an IPv4 port is rejected on an IPv4-only exit, but an IPv6 port
+   * is NOT rejected (all IPv6 addresses are rejected by
+   * policies_parse_exit_policy_internal on IPv4-only exits, so
+   * policies_parse_exit_policy_reject_private doesn't need to do anything
+   * with IPv6 addresses on IPv4-only exits) */
+  policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 1);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 1);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+  /* test that IPv4 and IPv6 ports are rejected on an IPv4/IPv6 exit */
+  policies_parse_exit_policy_reject_private(&policy, 1, NULL, 0, 1);
+  tt_assert(policy);
+  tt_assert(smartlist_len(policy) == 2);
+  tt_assert(test_policy_has_address_helper(policy, &ipv4_port->addr));
+  tt_assert(test_policy_has_address_helper(policy, &ipv6_port->addr));
+  addr_policy_list_free(policy);
+  policy = NULL;
+
+done:
+  addr_policy_list_free(policy);
+  if (test_configured_ports) {
+    SMARTLIST_FOREACH(test_configured_ports,
+                      port_cfg_t *, p, port_cfg_free(p));
+    smartlist_free(test_configured_ports);
+    test_configured_ports = NULL;
+  }
+  UNMOCK(get_configured_ports);
+}
+
+#undef TEST_IPV4_ADDR
+#undef TEST_IPV6_ADDR
+
+/** Run unit tests for rejecting publicly routable interface addresses on this
+ * exit relay using policies_parse_exit_policy_reject_private */
+static void
+test_policies_reject_interface_address(void *arg)
+{
+  smartlist_t *policy = NULL;
+  smartlist_t *public_ipv4_addrs =
+    get_interface_address6_list(LOG_INFO, AF_INET, 0);
+  smartlist_t *public_ipv6_addrs =
+    get_interface_address6_list(LOG_INFO, AF_INET6, 0);
+  (void)arg;
+
+  /* test that no addresses are rejected when none are supplied/requested */
+  policies_parse_exit_policy_reject_private(&policy, 0, NULL, 0, 0);
+  tt_assert(policy == NULL);
+
+  /* test that only IPv4 interface addresses are rejected on an IPv4-only exit
+   */
+  policies_parse_exit_policy_reject_private(&policy, 0, NULL, 1, 0);
+  if (policy) {
+    tt_assert(smartlist_len(policy) == smartlist_len(public_ipv4_addrs));
+    addr_policy_list_free(policy);
+    policy = NULL;
+  }
+
+  /* test that IPv4 and IPv6 interface addresses are rejected on an IPv4/IPv6
+   * exit */
+  policies_parse_exit_policy_reject_private(&policy, 0, NULL, 1, 0);
+  if (policy) {
+    tt_assert(smartlist_len(policy) == (smartlist_len(public_ipv4_addrs)
+                                        + smartlist_len(public_ipv6_addrs)));
+    addr_policy_list_free(policy);
+    policy = NULL;
+  }
+
+ done:
+  addr_policy_list_free(policy);
+  free_interface_address6_list(public_ipv4_addrs);
+  free_interface_address6_list(public_ipv6_addrs);
+}
+
 static void
 test_dump_exit_policy_to_string(void *arg)
 {
@@ -578,10 +825,149 @@ test_dump_exit_policy_to_string(void *arg)
  tor_free(ep);
 }
 
+static routerinfo_t *mock_desc_routerinfo = NULL;
+const routerinfo_t *mock_router_get_my_routerinfo(void)
+{
+  return mock_desc_routerinfo;
+}
+
+#define DEFAULT_POLICY_STRING "reject *:*"
+#define TEST_IPV4_ADDR (0x02040608)
+#define TEST_IPV6_ADDR ("2003::ef01")
+
+static or_options_t mock_options;
+
+static const or_options_t *
+mock_get_options(void)
+{
+  return &mock_options;
+}
+
+/** Run unit tests for generating summary lines of exit policies */
+static void
+test_policies_getinfo_helper_policies(void *arg)
+{
+  (void)arg;
+  int rv = 0;
+  size_t ipv4_len = 0, ipv6_len = 0;
+  char *answer = NULL;
+  const char *errmsg = NULL;
+  routerinfo_t mock_my_routerinfo;
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/default", &answer, &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  tt_assert(strlen(answer) > 0);
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/default",
+                               &answer, &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  tt_assert(strlen(answer) > 0);
+  tor_free(answer);
+
+  memset(&mock_my_routerinfo, 0, sizeof(routerinfo_t));
+  MOCK(router_get_my_routerinfo, mock_router_get_my_routerinfo);
+  mock_my_routerinfo.exit_policy = smartlist_new();
+  mock_desc_routerinfo = &mock_my_routerinfo;
+
+  memset(&mock_options, 0, sizeof(or_options_t));
+  MOCK(get_options, mock_get_options);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay",
+                               &answer, &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  tt_assert(strlen(answer) == 0);
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  ipv4_len = strlen(answer);
+  tt_assert(ipv4_len == 0 || ipv4_len == strlen(DEFAULT_POLICY_STRING));
+  tt_assert(ipv4_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING));
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  ipv6_len = strlen(answer);
+  tt_assert(ipv6_len == 0 || ipv6_len == strlen(DEFAULT_POLICY_STRING));
+  tt_assert(ipv6_len == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING));
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  /* It's either empty or it's the default */
+  tt_assert(strlen(answer) == 0 || !strcasecmp(answer, DEFAULT_POLICY_STRING));
+  tor_free(answer);
+
+  mock_my_routerinfo.addr = TEST_IPV4_ADDR;
+  tor_addr_parse(&mock_my_routerinfo.ipv6_addr, TEST_IPV6_ADDR);
+  append_exit_policy_string(&mock_my_routerinfo.exit_policy, "accept *4:*");
+  append_exit_policy_string(&mock_my_routerinfo.exit_policy, "reject *6:*");
+
+  mock_options.IPv6Exit = 1;
+  tor_addr_from_ipv4h(&mock_options.OutboundBindAddressIPv4_, TEST_IPV4_ADDR);
+  tor_addr_parse(&mock_options.OutboundBindAddressIPv6_, TEST_IPV6_ADDR);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/reject-private/relay",
+                               &answer, &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  tt_assert(strlen(answer) > 0);
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/ipv4", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  ipv4_len = strlen(answer);
+  tt_assert(ipv4_len > 0);
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/ipv6", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  ipv6_len = strlen(answer);
+  tt_assert(ipv6_len > 0);
+  tor_free(answer);
+
+  rv = getinfo_helper_policies(NULL, "exit-policy/full", &answer,
+                               &errmsg);
+  tt_assert(rv == 0);
+  tt_assert(answer != NULL);
+  tt_assert(strlen(answer) > 0);
+  tt_assert(strlen(answer) == ipv4_len + ipv6_len + 1);
+  tor_free(answer);
+
+done:
+  tor_free(answer);
+  UNMOCK(get_options);
+  UNMOCK(router_get_my_routerinfo);
+  smartlist_free(mock_my_routerinfo.exit_policy);
+}
+
+#undef DEFAULT_POLICY_STRING
+#undef TEST_IPV4_ADDR
+#undef TEST_IPV6_ADDR
+
 struct testcase_t policy_tests[] = {
   { "router_dump_exit_policy_to_string", test_dump_exit_policy_to_string, 0,
     NULL, NULL },
   { "general", test_policies_general, 0, NULL, NULL },
+  { "getinfo_helper_policies", test_policies_getinfo_helper_policies, 0, NULL,
+    NULL },
+  { "reject_exit_address", test_policies_reject_exit_address, 0, NULL, NULL },
+  { "reject_interface_address", test_policies_reject_interface_address, 0, NULL, NULL },
+  { "reject_port_address", test_policies_reject_port_address, 0, NULL, NULL },
   END_OF_TESTCASES
 };