Browse Source

Merge branch 'bug7555_v2_squashed'

Conflicts:
	src/or/connection_edge.c
Nick Mathewson 9 years ago
parent
commit
1053af0b9c

+ 4 - 0
changes/bug14193

@@ -0,0 +1,4 @@
+  o Minor bugfixes (client DNS):
+    - Report the correct cached DNS expiration times. Previously, we
+      would report everything as "never expires."  Fixes bug 14193;
+      bugfix on 0.2.3.17-beta.

+ 6 - 0
changes/bug14259

@@ -0,0 +1,6 @@
+  o Minor bugfixes (client):
+    - Avoid a small memory leak when we find a cached answer for a reverse
+      DNS lookup in a client-side DNS cache. (Remember, client-side DNS
+      caching is off by default, and is not recommended.) Fixes bug 14259;
+      bugfix on 0.2.0.1-alpha.
+

+ 5 - 0
changes/bug7555

@@ -0,0 +1,5 @@
+  o Major bugfixes (client):
+    - Allow MapAddress and AutomapHostsOnResolve to work together when an
+      address is mapped into another address type that must be
+      automapped at resolve time.  Fixes bug 7555; bugfix on
+      0.2.0.1-alpha.

+ 34 - 8
src/or/addressmap.c

@@ -390,13 +390,35 @@ addressmap_rewrite(char *address, size_t maxlen,
       goto done;
     }
 
-    if (ent && ent->source == ADDRMAPSRC_DNS) {
-      sa_family_t f;
-      tor_addr_t tmp;
-      f = tor_addr_parse(&tmp, ent->new_address);
-      if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS))
-        goto done;
-      else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS))
+    switch (ent->source) {
+      case ADDRMAPSRC_DNS:
+        {
+          sa_family_t f;
+          tor_addr_t tmp;
+          f = tor_addr_parse(&tmp, ent->new_address);
+          if (f == AF_INET && !(flags & AMR_FLAG_USE_IPV4_DNS))
+            goto done;
+          else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS))
+            goto done;
+        }
+        break;
+      case ADDRMAPSRC_CONTROLLER:
+      case ADDRMAPSRC_TORRC:
+        if (!(flags & AMR_FLAG_USE_MAPADDRESS))
+          goto done;
+        break;
+      case ADDRMAPSRC_AUTOMAP:
+        if (!(flags & AMR_FLAG_USE_AUTOMAP))
+          goto done;
+        break;
+      case ADDRMAPSRC_TRACKEXIT:
+        if (!(flags & AMR_FLAG_USE_TRACKEXIT))
+          goto done;
+        break;
+      case ADDRMAPSRC_NONE:
+      default:
+        log_warn(LD_BUG, "Unknown addrmap source value %d. Ignoring it.",
+                 (int) ent->source);
         goto done;
     }
 
@@ -431,7 +453,7 @@ addressmap_rewrite(char *address, size_t maxlen,
   if (exit_source_out)
     *exit_source_out = exit_source;
   if (expires_out)
-    *expires_out = TIME_MAX;
+    *expires_out = expires;
   return (rewrites > 0);
 }
 
@@ -455,6 +477,8 @@ addressmap_rewrite_reverse(char *address, size_t maxlen, unsigned flags,
       return 0;
     else if (f == AF_INET6 && !(flags & AMR_FLAG_USE_IPV6_DNS))
       return 0;
+    /* FFFF we should reverse-map virtual addresses even if we haven't
+     * enabled DNS cacheing. */
   }
 
   tor_asprintf(&s, "REVERSE[%s]", address);
@@ -981,6 +1005,8 @@ addressmap_register_virtual_address(int type, char *new_address)
     strmap_set(virtaddress_reversemap, new_address, vent);
   addressmap_register(*addrp, new_address, 2, ADDRMAPSRC_AUTOMAP, 0, 0);
 
+  /* FFFF register corresponding reverse mapping. */
+
 #if 0
   {
     /* Try to catch possible bugs */

+ 5 - 2
src/or/addressmap.h

@@ -16,8 +16,11 @@ void addressmap_clean(time_t now);
 void addressmap_clear_configured(void);
 void addressmap_clear_transient(void);
 void addressmap_free_all(void);
-#define AMR_FLAG_USE_IPV4_DNS (1u<<0)
-#define AMR_FLAG_USE_IPV6_DNS (1u<<1)
+#define AMR_FLAG_USE_IPV4_DNS   (1u<<0)
+#define AMR_FLAG_USE_IPV6_DNS   (1u<<1)
+#define AMR_FLAG_USE_MAPADDRESS (1u<<2)
+#define AMR_FLAG_USE_AUTOMAP    (1u<<3)
+#define AMR_FLAG_USE_TRACKEXIT  (1u<<4)
 int addressmap_rewrite(char *address, size_t maxlen, unsigned flags,
                        time_t *expires_out,
                        addressmap_entry_source_t *exit_source_out);

+ 265 - 87
src/or/connection_edge.c

@@ -908,64 +908,83 @@ connection_ap_rewrite_and_attach_if_allowed(entry_connection_t *conn,
   return connection_ap_handshake_rewrite_and_attach(conn, circ, cpath);
 }
 
-/** Connection <b>conn</b> just finished its socks handshake, or the
- * controller asked us to take care of it. If <b>circ</b> is defined,
- * then that's where we'll want to attach it. Otherwise we have to
- * figure it out ourselves.
- *
- * First, parse whether it's a .exit address, remap it, and so on. Then
- * if it's for a general circuit, try to attach it to a circuit (or launch
- * one as needed), else if it's for a rendezvous circuit, fetch a
- * rendezvous descriptor first (or attach/launch a circuit if the
- * rendezvous descriptor is already here and fresh enough).
- *
- * The stream will exit from the hop
- * indicated by <b>cpath</b>, or from the last hop in circ's cpath if
- * <b>cpath</b> is NULL.
+/* Try to perform any map-based rewriting of the target address in
+ * <b>conn</b>, filling in the fields of <b>out</b> as we go, and modifying
+ * conn->socks_request.address as appropriate.
  */
-int
-connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
-                                           origin_circuit_t *circ,
-                                           crypt_path_t *cpath)
+STATIC void
+connection_ap_handshake_rewrite(entry_connection_t *conn,
+                                rewrite_result_t *out)
 {
   socks_request_t *socks = conn->socks_request;
-  hostname_type_t addresstype;
   const or_options_t *options = get_options();
   tor_addr_t addr_tmp;
-  /* We set this to true if this is an address we should automatically
-   * remap to a local address in VirtualAddrNetwork */
-  int automap = 0;
-  char orig_address[MAX_SOCKS_ADDR_LEN];
-  time_t map_expires = TIME_MAX;
-  time_t now = time(NULL);
-  connection_t *base_conn = ENTRY_TO_CONN(conn);
-  addressmap_entry_source_t exit_source = ADDRMAPSRC_NONE;
 
-  tor_strlower(socks->address); /* normalize it */
-  strlcpy(orig_address, socks->address, sizeof(orig_address));
+  /* Initialize all the fields of 'out' to reasonable defaults */
+  out->automap = 0;
+  out->exit_source = ADDRMAPSRC_NONE;
+  out->map_expires = TIME_MAX;
+  out->end_reason = 0;
+  out->should_close = 0;
+  out->orig_address[0] = 0;
+
+  /* We convert all incoming addresses to lowercase. */
+  tor_strlower(socks->address);
+  /* Remember the original address. */
+  strlcpy(out->orig_address, socks->address, sizeof(out->orig_address));
   log_debug(LD_APP,"Client asked for %s:%d",
             safe_str_client(socks->address),
             socks->port);
 
+  /* Check for whether this is a .exit address.  By default, those are
+   * disallowed when they're coming straight from the client, but you're
+   * allowed to have them in MapAddress commands and so forth. */
   if (!strcmpend(socks->address, ".exit") && !options->AllowDotExit) {
     log_warn(LD_APP, "The  \".exit\" notation is disabled in Tor due to "
              "security risks. Set AllowDotExit in your torrc to enable "
              "it (at your own risk).");
     control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                 escaped(socks->address));
-    connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
-    return -1;
+    out->end_reason = END_STREAM_REASON_TORPROTOCOL;
+    out->should_close = 1;
+    return;
   }
 
-  if (! conn->original_dest_address)
+  /* Remember the original address so we can tell the user about what
+   * they actually said, not just what it turned into. */
+  if (! conn->original_dest_address) {
+    /* Is the 'if' necessary here? XXXX */
     conn->original_dest_address = tor_strdup(conn->socks_request->address);
+  }
+
+  /* First, apply MapAddress and MAPADDRESS mappings. We need to do
+   * these only for non-reverse lookups, since they don't exist for those.
+   * We need to do this before we consider automapping, since we might
+   * e.g. resolve irc.oftc.net into irconionaddress.onion, at which point
+   * we'd need to automap it. */
+  if (socks->command != SOCKS_COMMAND_RESOLVE_PTR) {
+    const unsigned rewrite_flags = AMR_FLAG_USE_MAPADDRESS;
+    if (addressmap_rewrite(socks->address, sizeof(socks->address),
+                       rewrite_flags, &out->map_expires, &out->exit_source)) {
+      control_event_stream_status(conn, STREAM_EVENT_REMAP,
+                                  REMAP_STREAM_SOURCE_CACHE);
+    }
+  }
 
+  /* Now, handle automapping.  Automapping happens when we're asked to
+   * resolve a hostname, and AutomapHostsOnResolve is set, and
+   * the hostname has a suffix listed in AutomapHostsSuffixes.
+   */
   if (socks->command == SOCKS_COMMAND_RESOLVE &&
       tor_addr_parse(&addr_tmp, socks->address)<0 &&
       options->AutomapHostsOnResolve) {
-    automap = addressmap_address_should_automap(socks->address, options);
-    if (automap) {
+    /* Check the suffix... */
+    out->automap = addressmap_address_should_automap(socks->address, options);
+    if (out->automap) {
+      /* If we get here, then we should apply an automapping for this. */
       const char *new_addr;
+      /* We return an IPv4 address by default, or an IPv6 address if we
+       * are allowed to do so. */
       int addr_type = RESOLVED_TYPE_IPV4;
       if (conn->socks_request->socks_version != 4) {
         if (!conn->entry_cfg.ipv4_traffic ||
@@ -973,13 +992,18 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
             conn->entry_cfg.prefer_ipv6_virtaddr)
           addr_type = RESOLVED_TYPE_IPV6;
       }
+      /* Okay, register the target address as automapped, and find the new
+       * address we're supposed to give as a resolve answer.  (Return a cached
+       * value if we've looked up this address before.
+       */
       new_addr = addressmap_register_virtual_address(
                                     addr_type, tor_strdup(socks->address));
       if (! new_addr) {
         log_warn(LD_APP, "Unable to automap address %s",
                  escaped_safe_str(socks->address));
-        connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL);
-        return -1;
+        out->end_reason = END_STREAM_REASON_INTERNAL;
+        out->should_close = 1;
+        return;
       }
       log_info(LD_APP, "Automapping %s to %s",
                escaped_safe_str_client(socks->address),
@@ -988,6 +1012,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
     }
   }
 
+  /* Now handle reverse lookups, if they're in the cache.  This doesn't
+   * happen too often, since client-side DNS caching is off by default. */
   if (socks->command == SOCKS_COMMAND_RESOLVE_PTR) {
     unsigned rewrite_flags = 0;
     if (conn->entry_cfg.use_cached_ipv4_answers)
@@ -996,20 +1022,25 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       rewrite_flags |= AMR_FLAG_USE_IPV6_DNS;
 
     if (addressmap_rewrite_reverse(socks->address, sizeof(socks->address),
-                                   rewrite_flags, &map_expires)) {
+                                   rewrite_flags, &out->map_expires)) {
       char *result = tor_strdup(socks->address);
       /* remember _what_ is supposed to have been resolved. */
       tor_snprintf(socks->address, sizeof(socks->address), "REVERSE[%s]",
-                  orig_address);
+                   out->orig_address);
       connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_HOSTNAME,
                                              strlen(result), (uint8_t*)result,
                                              -1,
-                                             map_expires);
-      connection_mark_unattached_ap(conn,
-                                END_STREAM_REASON_DONE |
-                                END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
-      return 0;
+                                             out->map_expires);
+      tor_free(result);
+      out->end_reason = END_STREAM_REASON_DONE |
+                        END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED;
+      out->should_close = 1;
+      return;
     }
+
+    /* Hang on, did we find an answer saying that this is a reverse lookup for
+     * an internal address?  If so, we should reject it if we're condigured to
+     * do so. */
     if (options->ClientDNSRejectInternalAddresses) {
       /* Don't let people try to do a reverse lookup on 10.0.0.1. */
       tor_addr_t addr;
@@ -1019,43 +1050,108 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       if (ok == 1 && tor_addr_is_internal(&addr, 0)) {
         connection_ap_handshake_socks_resolved(conn, RESOLVED_TYPE_ERROR,
                                                0, NULL, -1, TIME_MAX);
-        connection_mark_unattached_ap(conn,
-                                 END_STREAM_REASON_SOCKSPROTOCOL |
-                                 END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
-        return -1;
+        out->end_reason = END_STREAM_REASON_SOCKSPROTOCOL |
+                          END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED;
+        out->should_close = 1;
+        return;
       }
     }
-  } else if (!automap) {
-    /* For address map controls, remap the address. */
-    unsigned rewrite_flags = 0;
+  }
+
+  /* If we didn't automap it before, then this is still the address
+   * that came straight from the user, mapped according to any
+   * MapAddress/MAPADDRESS commands.  Now other mappings, including
+   * previously registered Automap entries, TrackHostExits entries,
+   * and client-side DNS cache entries (not recommended).
+   */
+  if (!socks->command != SOCKS_COMMAND_RESOLVE_PTR &&
+      !out->automap) {
+    unsigned rewrite_flags = AMR_FLAG_USE_AUTOMAP | AMR_FLAG_USE_TRACKEXIT;
+    addressmap_entry_source_t exit_source2;
     if (conn->entry_cfg.use_cached_ipv4_answers)
       rewrite_flags |= AMR_FLAG_USE_IPV4_DNS;
     if (conn->entry_cfg.use_cached_ipv6_answers)
       rewrite_flags |= AMR_FLAG_USE_IPV6_DNS;
     if (addressmap_rewrite(socks->address, sizeof(socks->address),
-                           rewrite_flags, &map_expires, &exit_source)) {
+                        rewrite_flags, &out->map_expires, &exit_source2)) {
       control_event_stream_status(conn, STREAM_EVENT_REMAP,
                                   REMAP_STREAM_SOURCE_CACHE);
     }
+    if (out->exit_source == ADDRMAPSRC_NONE) {
+      /* If it wasn't a .exit before, maybe it turned into a .exit. Remember
+       * the original source of a .exit. */
+      out->exit_source = exit_source2;
+    }
   }
 
-  if (!automap && address_is_in_virtual_range(socks->address)) {
-    /* This address was probably handed out by client_dns_get_unmapped_address,
-     * but the mapping was discarded for some reason.  We *don't* want to send
-     * the address through Tor; that's likely to fail, and may leak
-     * information.
+  /* Check to see whether we're about to use an address in the virtual
+   * range without actually having gotten it from an Automap. */
+  if (!out->automap && address_is_in_virtual_range(socks->address)) {
+    /* This address was probably handed out by
+     * client_dns_get_unmapped_address, but the mapping was discarded for some
+     * reason.  Or the user typed in a virtual address range manually.  We
+     * *don't* want to send the address through Tor; that's likely to fail,
+     * and may leak information.
      */
     log_warn(LD_APP,"Missing mapping for virtual address '%s'. Refusing.",
              safe_str_client(socks->address));
-    connection_mark_unattached_ap(conn, END_STREAM_REASON_INTERNAL);
-    return -1;
+    out->end_reason = END_STREAM_REASON_INTERNAL;
+    out->should_close = 1;
+    return;
+  }
+}
+
+/** Connection <b>conn</b> just finished its socks handshake, or the
+ * controller asked us to take care of it. If <b>circ</b> is defined,
+ * then that's where we'll want to attach it. Otherwise we have to
+ * figure it out ourselves.
+ *
+ * First, parse whether it's a .exit address, remap it, and so on. Then
+ * if it's for a general circuit, try to attach it to a circuit (or launch
+ * one as needed), else if it's for a rendezvous circuit, fetch a
+ * rendezvous descriptor first (or attach/launch a circuit if the
+ * rendezvous descriptor is already here and fresh enough).
+ *
+ * The stream will exit from the hop
+ * indicated by <b>cpath</b>, or from the last hop in circ's cpath if
+ * <b>cpath</b> is NULL.
+ */
+int
+connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
+                                           origin_circuit_t *circ,
+                                           crypt_path_t *cpath)
+{
+  socks_request_t *socks = conn->socks_request;
+  const or_options_t *options = get_options();
+  connection_t *base_conn = ENTRY_TO_CONN(conn);
+  time_t now = time(NULL);
+  rewrite_result_t rr;
+
+  memset(&rr, 0, sizeof(rr));
+  connection_ap_handshake_rewrite(conn,&rr);
+
+  if (rr.should_close) {
+    /* connection_ap_handshake_rewrite told us to close the connection,
+     * either because it sent back an answer, or because it sent back an
+     * error */
+    connection_mark_unattached_ap(conn, rr.end_reason);
+    if (END_STREAM_REASON_DONE == (rr.end_reason & END_STREAM_REASON_MASK))
+      return 0;
+    else
+      return -1;
   }
 
+  const time_t map_expires = rr.map_expires;
+  const int automap = rr.automap;
+  const addressmap_entry_source_t exit_source = rr.exit_source;
+
   /* Parse the address provided by SOCKS.  Modify it in-place if it
    * specifies a hidden-service (.onion) or particular exit node (.exit).
    */
-  addresstype = parse_extended_hostname(socks->address);
+  const hostname_type_t addresstype = parse_extended_hostname(socks->address);
 
+  /* Now see whether the hostname is bogus.  This could happen because of an
+   * onion hostname whose format we don't recognize. */
   if (addresstype == BAD_HOSTNAME) {
     control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                 escaped(socks->address));
@@ -1063,16 +1159,21 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
     return -1;
   }
 
+  /* If this is a .exit hostname, strip off the .name.exit part, and
+   * see whether we're going to connect there, and otherwise handle it.
+   * (The ".exit" part got stripped off by "parse_extended_hostname").
+   *
+   * We'll set chosen_exit_name and/or close the connection as appropriate.
+   */
   if (addresstype == EXIT_HOSTNAME) {
-    /* foo.exit -- modify conn->chosen_exit_node to specify the exit
-     * node, and conn->address to hold only the address portion. */
-    char *s = strrchr(socks->address,'.');
-
-    /* If StrictNodes is not set, then .exit overrides ExcludeNodes. */
+    /* If StrictNodes is not set, then .exit overrides ExcludeNodes but
+     * not ExcludeExitNodes. */
     routerset_t *excludeset = options->StrictNodes ?
       options->ExcludeExitNodesUnion_ : options->ExcludeExitNodes;
-    const node_t *node;
+    const node_t *node = NULL;
 
+    /* If this .exit was added by an AUTOMAP, then it came straight from
+     * a user.  Make sure that options->AllowDotExit permits that. */
     if (exit_source == ADDRMAPSRC_AUTOMAP && !options->AllowDotExit) {
       /* Whoops; this one is stale.  It must have gotten added earlier,
        * when AllowDotExit was on. */
@@ -1085,6 +1186,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       return -1;
     }
 
+    /* Double-check to make sure there are no .exits coming from
+     * impossible/weird sources. */
     if (exit_source == ADDRMAPSRC_DNS ||
         (exit_source == ADDRMAPSRC_NONE && !options->AllowDotExit)) {
       /* It shouldn't be possible to get a .exit address from any of these
@@ -1099,9 +1202,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
     }
 
     tor_assert(!automap);
+    /* Now, find the character before the .(name) part. */
+    char *s = strrchr(socks->address,'.');
     if (s) {
       /* The address was of the form "(stuff).(name).exit */
       if (s[1] != '\0') {
+        /* Looks like a real .exit one. */
         conn->chosen_exit_name = tor_strdup(s+1);
         node = node_get_by_nickname(conn->chosen_exit_name, 1);
 
@@ -1120,7 +1226,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
         return -1;
       }
     } else {
-      /* It looks like they just asked for "foo.exit". */
+      /* It looks like they just asked for "foo.exit".  That's a special
+       * form that means (foo's address).foo.exit. */
 
       conn->chosen_exit_name = tor_strdup(socks->address);
       node = node_get_by_nickname(conn->chosen_exit_name, 1);
@@ -1129,6 +1236,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
         node_get_address_string(node, socks->address, sizeof(socks->address));
       }
     }
+
     /* Now make sure that the chosen exit exists... */
     if (!node) {
       log_warn(LD_APP,
@@ -1150,8 +1258,12 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
        implies no. */
   }
 
+  /* Now, handle everything that isn't a .onion address. */
   if (addresstype != ONION_HOSTNAME) {
-    /* not a hidden-service request (i.e. normal or .exit) */
+    /* Not a hidden-service request.  It's either a hostname or an IP,
+     * possibly with a .exit that we stripped off. */
+
+    /* Check for funny characters in the address. */
     if (address_is_invalid_destination(socks->address, 1)) {
       control_event_client_status(LOG_WARN, "SOCKS_BAD_HOSTNAME HOSTNAME=%s",
                                   escaped(socks->address));
@@ -1162,6 +1274,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       return -1;
     }
 
+    /* If we're running in Tor2webMode, we don't allow anything BUT .onion
+     * addresses. */
     if (options->Tor2webMode) {
       log_warn(LD_APP, "Refusing to connect to non-hidden-service hostname %s "
                "because tor2web mode is enabled.",
@@ -1170,12 +1284,15 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       return -1;
     }
 
+    /* See if this is a hostname lookup that we can answer immediately.
+     * (For example, an attempt to look up the IP address for an IP address.)
+     */
     if (socks->command == SOCKS_COMMAND_RESOLVE) {
       tor_addr_t answer;
       /* Reply to resolves immediately if we can. */
       if (tor_addr_parse(&answer, socks->address) >= 0) {/* is it an IP? */
         /* remember _what_ is supposed to have been resolved. */
-        strlcpy(socks->address, orig_address, sizeof(socks->address));
+        strlcpy(socks->address, rr.orig_address, sizeof(socks->address));
         connection_ap_handshake_socks_resolved_addr(conn, &answer, -1,
                                                     map_expires);
         connection_mark_unattached_ap(conn,
@@ -1186,14 +1303,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       tor_assert(!automap);
       rep_hist_note_used_resolve(now); /* help predict this next time */
     } else if (socks->command == SOCKS_COMMAND_CONNECT) {
+      /* Special handling for attempts to connect */
       tor_assert(!automap);
+      /* Don't allow connections to port 0. */
       if (socks->port == 0) {
         log_notice(LD_APP,"Application asked to connect to port 0. Refusing.");
         connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
         return -1;
       }
+      /* You can't make connections to internal addresses, by default.
+       * Exceptions are begindir requests (where the address is meaningless,
+       * or cases where you've hand-configured a particular exit, thereby
+       * making the local address meaningful. */
       if (options->ClientRejectInternalAddresses &&
           !conn->use_begindir && !conn->chosen_exit_name && !circ) {
+        /* If we reach this point then we don't want to allow internal
+         * addresses.  Check if we got one. */
         tor_addr_t addr;
         if (tor_addr_hostname_is_local(socks->address) ||
             (tor_addr_parse(&addr, socks->address) >= 0 &&
@@ -1228,31 +1353,47 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
           connection_mark_unattached_ap(conn, END_STREAM_REASON_PRIVATE_ADDR);
           return -1;
         }
-      }
+      } /* end "if we should check for internal addresses" */
 
+      /* Okay.  We're still doing a CONNECT, and it wasn't a private
+       * address.  Do special handling for literal IP addresses */
       {
         tor_addr_t addr;
         /* XXX Duplicate call to tor_addr_parse. */
         if (tor_addr_parse(&addr, socks->address) >= 0) {
+          /* If we reach this point, it's an IPv4 or an IPv6 address. */
           sa_family_t family = tor_addr_family(&addr);
+
+          /* XXXX bug: the second one should be "ipv6_traffic" */
           if ((family == AF_INET && ! conn->entry_cfg.ipv4_traffic) ||
               (family == AF_INET6 && ! conn->entry_cfg.ipv4_traffic)) {
+            /* You can't do an IPv4 address on a v6-only socks listener,
+             * or vice versa. */
             log_warn(LD_NET, "Rejecting SOCKS request for an IP address "
                      "family that this listener does not support.");
             connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
             return -1;
           } else if (family == AF_INET6 && socks->socks_version == 4) {
+            /* You can't make a socks4 request to an IPv6 address. Socks4
+             * doesn't support that. */
             log_warn(LD_NET, "Rejecting SOCKS4 request for an IPv6 address.");
             connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
             return -1;
           } else if (socks->socks_version == 4 && !conn->entry_cfg.ipv4_traffic) {
+            /* You can't do any kind of Socks4 request when IPv4 is forbidden.
+             *
+             * XXX raise this check outside the enclosing block? */
             log_warn(LD_NET, "Rejecting SOCKS4 request on a listener with "
                      "no IPv4 traffic supported.");
             connection_mark_unattached_ap(conn, END_STREAM_REASON_ENTRYPOLICY);
             return -1;
           } else if (family == AF_INET6) {
+            /* Tell the exit: we won't accept any ipv4 connection to an IPv6
+             * address. */
             conn->entry_cfg.ipv4_traffic = 0;
           } else if (family == AF_INET) {
+            /* Tell the exit: we won't accept any ipv6 connection to an IPv4
+             * address. */
             conn->entry_cfg.ipv6_traffic = 0;
           }
         }
@@ -1261,6 +1402,9 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       if (socks->socks_version == 4)
         conn->entry_cfg.ipv6_traffic = 0;
 
+      /* Still handling CONNECT. Now, check for exit enclaves.  (Which we
+       * don't do on BEGINDIR, or there is a chosen exit.)
+       */
       if (!conn->use_begindir && !conn->chosen_exit_name && !circ) {
         /* see if we can find a suitable enclave exit */
         const node_t *r =
@@ -1277,11 +1421,13 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
         }
       }
 
-      /* warn or reject if it's using a dangerous port */
+      /* Still handling CONNECT: warn or reject if it's using a dangerous
+       * port. */
       if (!conn->use_begindir && !conn->chosen_exit_name && !circ)
         if (consider_plaintext_ports(conn, socks->port) < 0)
           return -1;
 
+      /* Remember the port so that we do predicted requests there. */
       if (!conn->use_begindir) {
         /* help predict this next time */
         rep_hist_note_used_port(now, socks->port);
@@ -1290,25 +1436,41 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       rep_hist_note_used_resolve(now); /* help predict this next time */
       /* no extra processing needed */
     } else {
+      /* We should only be doing CONNECT or RESOLVE! */
       tor_fragile_assert();
     }
+
+    /* Okay. At this point we've set chosen_exit_name if needed, rewritten the
+     * address, and decided not to reject it for any number of reasons. Now
+     * mark the connection as waiting for a circuit, and try to attach it!
+     */
     base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
-    if ((circ && connection_ap_handshake_attach_chosen_circuit(
-                   conn, circ, cpath) < 0) ||
-        (!circ &&
-         connection_ap_handshake_attach_circuit(conn) < 0)) {
+
+    /* If we were given a circuit to attach to, try to attach. Otherwise,
+     * try to find a good one and attach to that. */
+    int rv;
+    if (circ)
+      rv =  connection_ap_handshake_attach_chosen_circuit(conn, circ, cpath);
+    else
+      rv = connection_ap_handshake_attach_circuit(conn);
+
+    /* If the above function returned 0 then we're waiting for a circuit.
+     * if it returned 1, we're attached.  Both are okay.  But if it returned
+     * -1, there was an error, so make sure the connection is marked, and
+     * return -1. */
+    if (rv < 0) {
       if (!base_conn->marked_for_close)
         connection_mark_unattached_ap(conn, END_STREAM_REASON_CANT_ATTACH);
       return -1;
     }
+
     return 0;
   } else {
-    /* it's a hidden-service request */
-    rend_cache_entry_t *entry;
-    int r;
-    rend_service_authorization_t *client_auth;
-    rend_data_t *rend_data;
+    /* If we get here, it's a request for a .onion address! */
     tor_assert(!automap);
+
+    /* Check whether it's RESOLVE or RESOLVE_PTR.  We don't handle those
+     * for hidden service addresses. */
     if (SOCKS_COMMAND_IS_RESOLVE(socks->command)) {
       /* if it's a resolve request, fail it right now, rather than
        * building all the circuits and then realizing it won't work. */
@@ -1322,6 +1484,8 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       return -1;
     }
 
+    /* If we were passed a circuit, then we need to fail.  .onion addresses
+     * only work when we launch our own circuits for now. */
     if (circ) {
       log_warn(LD_CONTROL, "Attachstream to a circuit is not "
                "supported for .onion addresses currently. Failing.");
@@ -1329,15 +1493,22 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
       return -1;
     }
 
-    ENTRY_TO_EDGE_CONN(conn)->rend_data = rend_data =
+    /* Fill in the rend_data field so we can start doing a connection to
+     * a hidden service. */
+    rend_data_t *rend_data = ENTRY_TO_EDGE_CONN(conn)->rend_data =
       tor_malloc_zero(sizeof(rend_data_t));
     strlcpy(rend_data->onion_address, socks->address,
             sizeof(rend_data->onion_address));
     log_info(LD_REND,"Got a hidden service request for ID '%s'",
              safe_str_client(rend_data->onion_address));
-    /* see if we already have it cached */
-    r = rend_cache_lookup_entry(rend_data->onion_address, -1, &entry);
-    if (r<0) {
+
+    /* see if we already have a hidden service descriptor cached for this
+     * address. */
+    rend_cache_entry_t *entry = NULL;
+    const int rend_cache_lookup_result =
+      rend_cache_lookup_entry(rend_data->onion_address, -1, &entry);
+    if (rend_cache_lookup_result < 0) {
+      /* We should already have rejected this address! */
       log_warn(LD_BUG,"Invalid service name '%s'",
                safe_str_client(rend_data->onion_address));
       connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL);
@@ -1348,8 +1519,10 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
      * a stable circuit yet, but we know we'll need *something*. */
     rep_hist_note_used_internal(now, 0, 1);
 
-    /* Look up if we have client authorization for it. */
-    client_auth = rend_client_lookup_service_authorization(
+    /* Look up if we have client authorization configured for this hidden
+     * service.  If we do, associate it with the rend_data. */
+    rend_service_authorization_t *client_auth =
+      rend_client_lookup_service_authorization(
                                           rend_data->onion_address);
     if (client_auth) {
       log_info(LD_REND, "Using previously configured client authorization "
@@ -1358,12 +1531,16 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
              client_auth->descriptor_cookie, REND_DESC_COOKIE_LEN);
       rend_data->auth_type = client_auth->auth_type;
     }
-    if (r==0) {
+
+    /* Now, we either launch an attempt to connect to the hidden service,
+     * or we launch an attempt to look up its descriptor, depending on
+     * whether we had the descriptor. */
+    if (rend_cache_lookup_result == 0) {
       base_conn->state = AP_CONN_STATE_RENDDESC_WAIT;
       log_info(LD_REND, "Unknown descriptor %s. Fetching.",
                safe_str_client(rend_data->onion_address));
       rend_client_refetch_v2_renddesc(rend_data);
-    } else { /* r > 0 */
+    } else { /* rend_cache_lookup_result > 0 */
       base_conn->state = AP_CONN_STATE_CIRCUIT_WAIT;
       log_info(LD_REND, "Descriptor is here. Great.");
       if (connection_ap_handshake_attach_circuit(conn) < 0) {
@@ -1374,6 +1551,7 @@ connection_ap_handshake_rewrite_and_attach(entry_connection_t *conn,
     }
     return 0;
   }
+
   return 0; /* unreached but keeps the compiler happy */
 }
 

+ 25 - 0
src/or/connection_edge.h

@@ -143,6 +143,31 @@ STATIC int begin_cell_parse(const cell_t *cell, begin_cell_t *bcell,
 STATIC int connected_cell_format_payload(uint8_t *payload_out,
                                   const tor_addr_t *addr,
                                   uint32_t ttl);
+
+
+typedef struct {
+  /** Original address, after we lowercased it but before we started
+   * mapping it.
+   */
+  char orig_address[MAX_SOCKS_ADDR_LEN];
+  /** True iff the address has been automatically remapped to a local
+   * address in VirtualAddrNetwork.  (Only set true when we do a resolve
+   * and get a virtual address; not when we connect to the address.) */
+  int automap;
+  /** If this connection has a .exit address, who put it there? */
+  addressmap_entry_source_t exit_source;
+  /** If we've rewritten the address, when does this map expire? */
+  time_t map_expires;
+  /** If we should close the connection, this is the end_reason to pass
+   * to connection_mark_unattached_ap */
+  int end_reason;
+  /** True iff we should close the connection, either because of error or
+   * because of successful early RESOLVED reply. */
+  int should_close;
+} rewrite_result_t;
+
+STATIC void connection_ap_handshake_rewrite(entry_connection_t *conn,
+                                            rewrite_result_t *out);
 #endif
 
 #endif

+ 1 - 0
src/test/include.am

@@ -31,6 +31,7 @@ src_test_test_SOURCES = \
 	src/test/test_data.c \
 	src/test/test_dir.c \
 	src/test/test_checkdir.c \
+	src/test/test_entryconn.c \
 	src/test/test_entrynodes.c \
 	src/test/test_extorport.c \
 	src/test/test_introduce.c \

+ 2 - 0
src/test/test.c

@@ -1313,6 +1313,7 @@ extern struct testcase_t channel_tests[];
 extern struct testcase_t channeltls_tests[];
 extern struct testcase_t relay_tests[];
 extern struct testcase_t scheduler_tests[];
+extern struct testcase_t entryconn_tests[];
 
 static struct testgroup_t testgroups[] = {
   { "", test_array },
@@ -1337,6 +1338,7 @@ static struct testgroup_t testgroups[] = {
   { "circuitmux/", circuitmux_tests },
   { "options/", options_tests },
   { "entrynodes/", entrynodes_tests },
+  { "entryconn/", entryconn_tests },
   { "extorport/", extorport_tests },
   { "control/", controller_event_tests },
   { "hs/", hs_tests },

+ 1 - 2
src/test/test_config.c

@@ -51,8 +51,7 @@ test_config_addressmap(void *arg)
 
 /* Use old interface for now, so we don't need to rewrite the unit tests */
 #define addressmap_rewrite(a,s,eo,ao)                                   \
-  addressmap_rewrite((a),(s),AMR_FLAG_USE_IPV4_DNS|AMR_FLAG_USE_IPV6_DNS, \
-                     (eo),(ao))
+  addressmap_rewrite((a),(s), ~0, (eo),(ao))
 
   /* MapAddress .invalidwildcard.com .torserver.exit  - no match */
   strlcpy(address, "www.invalidwildcard.com", sizeof(address));

+ 770 - 0
src/test/test_entryconn.c

@@ -0,0 +1,770 @@
+/* Copyright (c) 2014-2015, The Tor Project, Inc. */
+/* See LICENSE for licensing information */
+
+#include "orconfig.h"
+
+#define CONNECTION_PRIVATE
+#define CONNECTION_EDGE_PRIVATE
+
+#include "or.h"
+#include "test.h"
+
+#include "addressmap.h"
+#include "config.h"
+#include "confparse.h"
+#include "connection.h"
+#include "connection_edge.h"
+
+static void *
+entryconn_rewrite_setup(const struct testcase_t *tc)
+{
+  (void)tc;
+  entry_connection_t *ec = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  addressmap_init();
+  return ec;
+}
+
+static int
+entryconn_rewrite_teardown(const struct testcase_t *tc, void *arg)
+{
+  (void)tc;
+  entry_connection_t *ec = arg;
+  if (ec)
+    connection_free_(ENTRY_TO_CONN(ec));
+  addressmap_free_all();
+  return 1;
+}
+
+static struct testcase_setup_t test_rewrite_setup = {
+  entryconn_rewrite_setup, entryconn_rewrite_teardown
+};
+
+/* Simple rewrite: no changes needed */
+static void
+test_entryconn_rewrite_basic(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+
+  tt_assert(ec->socks_request);
+  strlcpy(ec->socks_request->address, "www.TORproject.org",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.torproject.org");
+  tt_str_op(ec->socks_request->address, OP_EQ, "www.torproject.org");
+  tt_str_op(ec->original_dest_address, OP_EQ, "www.torproject.org");
+
+ done:
+  ;
+}
+
+/* Rewrite but reject because of disallowed .exit */
+static void
+test_entryconn_rewrite_bad_dotexit(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+
+  get_options_mutable()->AllowDotExit = 0;
+  tt_assert(ec->socks_request);
+  strlcpy(ec->socks_request->address, "www.TORproject.org.foo.exit",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 1);
+  tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_TORPROTOCOL);
+
+ done:
+  ;
+}
+
+/* Automap on resolve, connect to automapped address, resolve again and get
+ * same answer. (IPv4) */
+static void
+test_entryconn_rewrite_automap_ipv4(void *arg)
+{
+  entry_connection_t *ec = arg;
+  entry_connection_t *ec2=NULL, *ec3=NULL;
+  rewrite_result_t rr;
+  char *msg = NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup("."));
+  parse_virtual_addr_network("127.202.0.0/16", AF_INET, 0, &msg);
+
+  /* Automap this on resolve. */
+  strlcpy(ec->socks_request->address, "WWW.MIT.EDU",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu");
+  tt_str_op(ec->original_dest_address, OP_EQ, "www.mit.edu");
+
+  tt_assert(!strcmpstart(ec->socks_request->address,"127.202."));
+
+  /* Connect to it and make sure we get the original address back. */
+  strlcpy(ec2->socks_request->address, ec->socks_request->address,
+          sizeof(ec2->socks_request->address));
+
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->socks_request->address, OP_EQ, "www.mit.edu");
+
+  /* Resolve it again, make sure the answer is the same. */
+  strlcpy(ec3->socks_request->address, "www.MIT.EDU",
+          sizeof(ec3->socks_request->address));
+  ec3->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec3, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu");
+  tt_str_op(ec3->original_dest_address, OP_EQ, "www.mit.edu");
+
+  tt_str_op(ec3->socks_request->address, OP_EQ,
+            ec->socks_request->address);
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+  connection_free_(ENTRY_TO_CONN(ec3));
+}
+
+/* Automap on resolve, connect to automapped address, resolve again and get
+ * same answer. (IPv6) */
+static void
+test_entryconn_rewrite_automap_ipv6(void *arg)
+{
+  (void)arg;
+  entry_connection_t *ec =NULL;
+  entry_connection_t *ec2=NULL, *ec3=NULL;
+  rewrite_result_t rr;
+  char *msg = NULL;
+
+  ec = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+  ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes, tor_strdup("."));
+  parse_virtual_addr_network("FE80::/32", AF_INET6, 0, &msg);
+
+  /* Automap this on resolve. */
+  strlcpy(ec->socks_request->address, "WWW.MIT.EDU",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu");
+  tt_str_op(ec->original_dest_address, OP_EQ, "www.mit.edu");
+
+  /* Yes, this [ should be here. */
+  tt_assert(!strcmpstart(ec->socks_request->address,"[fe80:"));
+
+  /* Connect to it and make sure we get the original address back. */
+  strlcpy(ec2->socks_request->address, ec->socks_request->address,
+          sizeof(ec2->socks_request->address));
+
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->socks_request->address, OP_EQ, "www.mit.edu");
+
+  /* Resolve it again, make sure the answer is the same. */
+  strlcpy(ec3->socks_request->address, "www.MIT.EDU",
+          sizeof(ec3->socks_request->address));
+  ec3->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec3, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.mit.edu");
+  tt_str_op(ec3->original_dest_address, OP_EQ, "www.mit.edu");
+
+  tt_str_op(ec3->socks_request->address, OP_EQ,
+            ec->socks_request->address);
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec));
+  connection_free_(ENTRY_TO_CONN(ec2));
+  connection_free_(ENTRY_TO_CONN(ec3));
+}
+
+#if 0
+/* FFFF not actually supported. */
+/* automap on resolve, reverse lookup. */
+static void
+test_entryconn_rewrite_automap_reverse(void *arg)
+{
+  entry_connection_t *ec = arg;
+  entry_connection_t *ec2=NULL;
+  rewrite_result_t rr;
+  char *msg = NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  get_options_mutable()->SafeLogging_ = SAFELOG_SCRUB_NONE;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
+                tor_strdup(".bloom"));
+  parse_virtual_addr_network("127.80.0.0/16", AF_INET, 0, &msg);
+
+  /* Automap this on resolve. */
+  strlcpy(ec->socks_request->address, "www.poldy.BLOOM",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.poldy.bloom");
+  tt_str_op(ec->original_dest_address, OP_EQ, "www.poldy.bloom");
+
+  tt_assert(!strcmpstart(ec->socks_request->address,"127.80."));
+
+  strlcpy(ec2->socks_request->address, ec->socks_request->address,
+          sizeof(ec2->socks_request->address));
+  ec2->entry_cfg.use_cached_ipv4_answers = 1; // XXXX REMOVE.  This is only there to hide a bug.
+  ec2->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 1);
+  tt_int_op(rr.end_reason, OP_EQ,
+          END_STREAM_REASON_DONE|END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+}
+#endif
+
+/* Rewrite because of cached DNS entry. */
+static void
+test_entryconn_rewrite_cached_dns_ipv4(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+  time_t expires = time(NULL) + 3600;
+  entry_connection_t *ec2=NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  addressmap_register("www.friendly.example.com",
+                      tor_strdup("240.240.241.241"),
+                      expires,
+                      ADDRMAPSRC_DNS,
+                      0, 0);
+
+  strlcpy(ec->socks_request->address, "www.friendly.example.com",
+          sizeof(ec->socks_request->address));
+  strlcpy(ec2->socks_request->address, "www.friendly.example.com",
+          sizeof(ec2->socks_request->address));
+
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+
+  ec2->entry_cfg.use_cached_ipv4_answers = 1; /* only ec2 gets this flag */
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com");
+  tt_str_op(ec->socks_request->address, OP_EQ, "www.friendly.example.com");
+
+  connection_ap_handshake_rewrite(ec2, &rr);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, expires);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com");
+  tt_str_op(ec2->socks_request->address, OP_EQ, "240.240.241.241");
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+}
+
+/* Rewrite because of cached DNS entry. */
+static void
+test_entryconn_rewrite_cached_dns_ipv6(void *arg)
+{
+  entry_connection_t *ec = NULL;
+  rewrite_result_t rr;
+  time_t expires = time(NULL) + 3600;
+  entry_connection_t *ec2=NULL;
+
+  (void)arg;
+
+  ec  = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+
+  addressmap_register("www.friendly.example.com",
+                      tor_strdup("[::f00f]"),
+                      expires,
+                      ADDRMAPSRC_DNS,
+                      0, 0);
+
+  strlcpy(ec->socks_request->address, "www.friendly.example.com",
+          sizeof(ec->socks_request->address));
+  strlcpy(ec2->socks_request->address, "www.friendly.example.com",
+          sizeof(ec2->socks_request->address));
+
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+
+  ec2->entry_cfg.use_cached_ipv6_answers = 1; /* only ec2 gets this flag */
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com");
+  tt_str_op(ec->socks_request->address, OP_EQ, "www.friendly.example.com");
+
+  connection_ap_handshake_rewrite(ec2, &rr);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, expires);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "www.friendly.example.com");
+  tt_str_op(ec2->socks_request->address, OP_EQ, "[::f00f]");
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec));
+  connection_free_(ENTRY_TO_CONN(ec2));
+}
+
+/* Fail to connect to unmapped address in virtual range. */
+static void
+test_entryconn_rewrite_unmapped_virtual(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+  entry_connection_t *ec2 = NULL;
+  char *msg = NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET6);
+
+  parse_virtual_addr_network("18.202.0.0/16", AF_INET, 0, &msg);
+  parse_virtual_addr_network("[ABCD::]/16", AF_INET6, 0, &msg);
+
+  strlcpy(ec->socks_request->address, "18.202.5.5",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 1);
+  tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_INTERNAL);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+
+  strlcpy(ec2->socks_request->address, "[ABCD:9::5314:9543]",
+          sizeof(ec2->socks_request->address));
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 1);
+  tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_INTERNAL);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+}
+
+/* Rewrite because of mapaddress option */
+static void
+test_entryconn_rewrite_mapaddress(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+
+  config_line_append(&get_options_mutable()->AddressMap,
+                     "MapAddress", "meta metaobjects.example");
+  config_register_addressmaps(get_options());
+
+  strlcpy(ec->socks_request->address, "meta",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(ec->socks_request->address, OP_EQ, "metaobjects.example");
+
+ done:
+  ;
+}
+
+/* Reject reverse lookups of internal address. */
+static void
+test_entryconn_rewrite_reject_internal_reverse(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+
+  strlcpy(ec->socks_request->address, "10.0.0.1",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_RESOLVE_PTR;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.should_close, OP_EQ, 1);
+  tt_int_op(rr.end_reason, OP_EQ, END_STREAM_REASON_SOCKSPROTOCOL |
+            END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+
+ done:
+  ;
+}
+
+/* Rewrite into .exit because of virtual address mapping */
+static void
+test_entryconn_rewrite_automap_exit(void *arg)
+{
+  entry_connection_t *ec = arg;
+  entry_connection_t *ec2=NULL;
+  rewrite_result_t rr;
+  char *msg = NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  get_options_mutable()->AllowDotExit = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
+                tor_strdup(".EXIT"));
+  parse_virtual_addr_network("127.1.0.0/16", AF_INET, 0, &msg);
+
+  /* Automap this on resolve. */
+  strlcpy(ec->socks_request->address, "website.example.exit",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "website.example.exit");
+  tt_str_op(ec->original_dest_address, OP_EQ, "website.example.exit");
+
+  tt_assert(!strcmpstart(ec->socks_request->address,"127.1."));
+
+  /* Connect to it and make sure we get the original address back. */
+  strlcpy(ec2->socks_request->address, ec->socks_request->address,
+          sizeof(ec2->socks_request->address));
+
+  ec2->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_AUTOMAP);
+  tt_str_op(rr.orig_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->original_dest_address, OP_EQ, ec->socks_request->address);
+  tt_str_op(ec2->socks_request->address, OP_EQ, "website.example.exit");
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+}
+
+/* Rewrite into .exit because of mapaddress */
+static void
+test_entryconn_rewrite_mapaddress_exit(void *arg)
+{
+  entry_connection_t *ec = arg;
+  rewrite_result_t rr;
+
+  config_line_append(&get_options_mutable()->AddressMap,
+                     "MapAddress", "*.example.com  *.example.com.abc.exit");
+  config_register_addressmaps(get_options());
+
+  /* Automap this on resolve. */
+  strlcpy(ec->socks_request->address, "abc.example.com",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_TORRC);
+  tt_str_op(rr.orig_address, OP_EQ, "abc.example.com");
+  tt_str_op(ec->socks_request->address, OP_EQ, "abc.example.com.abc.exit");
+ done:
+  ;
+}
+
+/* Map foo.onion to longthing.onion, and also automap. */
+static void
+test_entryconn_rewrite_mapaddress_automap_onion(void *arg)
+{
+  entry_connection_t *ec = arg;
+  entry_connection_t *ec2 = NULL;
+  entry_connection_t *ec3 = NULL;
+  entry_connection_t *ec4 = NULL;
+  rewrite_result_t rr;
+  char *msg = NULL;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  ec4 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  get_options_mutable()->AllowDotExit = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
+                tor_strdup(".onion"));
+  parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
+  config_line_append(&get_options_mutable()->AddressMap,
+                     "MapAddress", "foo.onion abcdefghijklmnop.onion");
+  config_register_addressmaps(get_options());
+
+  /* Connect to foo.onion. */
+  strlcpy(ec->socks_request->address, "foo.onion",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "foo.onion");
+  tt_str_op(ec->socks_request->address, OP_EQ, "abcdefghijklmnop.onion");
+
+  /* Okay, resolve foo.onion */
+  strlcpy(ec2->socks_request->address, "foo.onion",
+          sizeof(ec2->socks_request->address));
+  ec2->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "foo.onion");
+  tt_assert(!strcmpstart(ec2->socks_request->address, "192.168."));
+
+  /* Now connect */
+  strlcpy(ec3->socks_request->address, ec2->socks_request->address,
+          sizeof(ec3->socks_request->address));
+  ec3->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec3, &rr);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_assert(!strcmpstart(ec3->socks_request->address, "abcdefghijklmnop.onion"));
+
+  /* Now resolve abcefghijklmnop.onion. */
+  strlcpy(ec4->socks_request->address, "abcdefghijklmnop.onion",
+          sizeof(ec4->socks_request->address));
+  ec4->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec4, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 1);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "abcdefghijklmnop.onion");
+  tt_assert(!strcmpstart(ec4->socks_request->address, "192.168."));
+  /* XXXX doesn't work
+     tt_str_op(ec4->socks_request->address, OP_EQ, ec2->socks_request->address);
+  */
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+  connection_free_(ENTRY_TO_CONN(ec3));
+  connection_free_(ENTRY_TO_CONN(ec4));
+}
+
+static void
+test_entryconn_rewrite_mapaddress_automap_onion_common(entry_connection_t *ec,
+                                                       int map_to_onion,
+                                                       int map_to_address)
+{
+  entry_connection_t *ec2 = NULL;
+  entry_connection_t *ec3 = NULL;
+  rewrite_result_t rr;
+
+  ec2 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+  ec3 = entry_connection_new(CONN_TYPE_AP, AF_INET);
+
+  /* Connect to irc.example.com */
+  strlcpy(ec->socks_request->address, "irc.example.com",
+          sizeof(ec->socks_request->address));
+  ec->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "irc.example.com");
+  tt_str_op(ec->socks_request->address, OP_EQ,
+            map_to_onion ? "abcdefghijklmnop.onion" : "irc.example.com");
+
+  /* Okay, resolve irc.example.com */
+  strlcpy(ec2->socks_request->address, "irc.example.com",
+          sizeof(ec2->socks_request->address));
+  ec2->socks_request->command = SOCKS_COMMAND_RESOLVE;
+  connection_ap_handshake_rewrite(ec2, &rr);
+
+  tt_int_op(rr.automap, OP_EQ, map_to_onion && map_to_address);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  tt_i64_op(rr.map_expires, OP_EQ, TIME_MAX);
+  tt_int_op(rr.exit_source, OP_EQ, ADDRMAPSRC_NONE);
+  tt_str_op(rr.orig_address, OP_EQ, "irc.example.com");
+  if (map_to_onion && map_to_address)
+    tt_assert(!strcmpstart(ec2->socks_request->address, "192.168."));
+
+  /* Now connect */
+  strlcpy(ec3->socks_request->address, ec2->socks_request->address,
+          sizeof(ec3->socks_request->address));
+  ec3->socks_request->command = SOCKS_COMMAND_CONNECT;
+  connection_ap_handshake_rewrite(ec3, &rr);
+  tt_int_op(rr.automap, OP_EQ, 0);
+  tt_int_op(rr.should_close, OP_EQ, 0);
+  tt_int_op(rr.end_reason, OP_EQ, 0);
+  if (map_to_onion)
+    tt_assert(!strcmpstart(ec3->socks_request->address,
+                           "abcdefghijklmnop.onion"));
+
+ done:
+  connection_free_(ENTRY_TO_CONN(ec2));
+  connection_free_(ENTRY_TO_CONN(ec3));
+}
+
+/* This time is the same, but we start with a mapping from a non-onion
+ * address. */
+static void
+test_entryconn_rewrite_mapaddress_automap_onion2(void *arg)
+{
+  char *msg = NULL;
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
+                tor_strdup(".onion"));
+  parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
+  config_line_append(&get_options_mutable()->AddressMap,
+                     "MapAddress", "irc.example.com abcdefghijklmnop.onion");
+  config_register_addressmaps(get_options());
+
+  test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 1, 1);
+}
+
+/* Same as above, with automapped turned off */
+static void
+test_entryconn_rewrite_mapaddress_automap_onion3(void *arg)
+{
+  config_line_append(&get_options_mutable()->AddressMap,
+                     "MapAddress", "irc.example.com abcdefghijklmnop.onion");
+  config_register_addressmaps(get_options());
+
+  test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 1, 0);
+}
+
+/* As above, with no mapping. */
+static void
+test_entryconn_rewrite_mapaddress_automap_onion4(void *arg)
+{
+  char *msg = NULL;
+  get_options_mutable()->AutomapHostsOnResolve = 1;
+  smartlist_add(get_options_mutable()->AutomapHostsSuffixes,
+                tor_strdup(".onion"));
+  parse_virtual_addr_network("192.168.0.0/16", AF_INET, 0, &msg);
+
+  test_entryconn_rewrite_mapaddress_automap_onion_common(arg, 0, 1);
+}
+
+
+#define REWRITE(name)                           \
+  { #name, test_entryconn_##name, TT_FORK, &test_rewrite_setup, NULL }
+
+struct testcase_t entryconn_tests[] = {
+  REWRITE(rewrite_basic),
+  REWRITE(rewrite_bad_dotexit),
+  REWRITE(rewrite_automap_ipv4),
+  REWRITE(rewrite_automap_ipv6),
+  // REWRITE(rewrite_automap_reverse),
+  REWRITE(rewrite_cached_dns_ipv4),
+  REWRITE(rewrite_cached_dns_ipv6),
+  REWRITE(rewrite_unmapped_virtual),
+  REWRITE(rewrite_mapaddress),
+  REWRITE(rewrite_reject_internal_reverse),
+  REWRITE(rewrite_automap_exit),
+  REWRITE(rewrite_mapaddress_exit),
+  REWRITE(rewrite_mapaddress_automap_onion),
+  REWRITE(rewrite_mapaddress_automap_onion2),
+  REWRITE(rewrite_mapaddress_automap_onion3),
+  REWRITE(rewrite_mapaddress_automap_onion4),
+
+  END_OF_TESTCASES
+};
+