Browse Source

Unit test dns_resolve(), dns_clip_ttl(), dns_get_expiry_ttl().

rl1987 8 years ago
parent
commit
77a5ca901f
8 changed files with 431 additions and 106 deletions
  1. 2 2
      src/or/connection.c
  2. 1 1
      src/or/connection.h
  3. 14 102
      src/or/dns.c
  4. 17 1
      src/or/dns.h
  5. 90 0
      src/or/dns_structs.h
  6. 1 0
      src/test/include.am
  7. 2 0
      src/test/test.c
  8. 304 0
      src/test/test_dns.c

+ 2 - 2
src/or/connection.c

@@ -654,8 +654,8 @@ connection_free_(connection_t *conn)
 
 /** Make sure <b>conn</b> isn't in any of the global conn lists; then free it.
  */
-void
-connection_free(connection_t *conn)
+MOCK_IMPL(void,
+connection_free,(connection_t *conn))
 {
   if (!conn)
     return;

+ 1 - 1
src/or/connection.h

@@ -28,7 +28,7 @@ listener_connection_t *listener_connection_new(int type, int socket_family);
 connection_t *connection_new(int type, int socket_family);
 
 void connection_link_connections(connection_t *conn_a, connection_t *conn_b);
-void connection_free(connection_t *conn);
+MOCK_DECL(void,connection_free,(connection_t *conn));
 void connection_free_all(void);
 void connection_about_to_close_connection(connection_t *conn);
 void connection_close_immediate(connection_t *conn);

+ 14 - 102
src/or/dns.c

@@ -11,6 +11,8 @@
  * be nonblocking.)
  **/
 
+#define DNS_PRIVATE
+
 #include "or.h"
 #include "circuitlist.h"
 #include "circuituse.h"
@@ -81,9 +83,6 @@ struct evdns_request;
 
 #endif
 
-/** Longest hostname we're willing to resolve. */
-#define MAX_ADDRESSLEN 256
-
 /** How long will we wait for an answer from the resolver before we decide
  * that the resolver is wedged? */
 #define RESOLVE_MAX_TIMEOUT 300
@@ -102,104 +101,16 @@ static char *resolv_conf_fname = NULL;
  * the nameservers?  Used to check whether we need to reconfigure. */
 static time_t resolv_conf_mtime = 0;
 
-/** Linked list of connections waiting for a DNS answer. */
-typedef struct pending_connection_t {
-  edge_connection_t *conn;
-  struct pending_connection_t *next;
-} pending_connection_t;
-
-/** Value of 'magic' field for cached_resolve_t.  Used to try to catch bad
- * pointers and memory stomping. */
-#define CACHED_RESOLVE_MAGIC 0x1234F00D
-
-/* Possible states for a cached resolve_t */
-/** We are waiting for the resolver system to tell us an answer here.
- * When we get one, or when we time out, the state of this cached_resolve_t
- * will become "DONE" and we'll possibly add a CACHED
- * entry. This cached_resolve_t will be in the hash table so that we will
- * know not to launch more requests for this addr, but rather to add more
- * connections to the pending list for the addr. */
-#define CACHE_STATE_PENDING 0
-/** This used to be a pending cached_resolve_t, and we got an answer for it.
- * Now we're waiting for this cached_resolve_t to expire.  This should
- * have no pending connections, and should not appear in the hash table. */
-#define CACHE_STATE_DONE 1
-/** We are caching an answer for this address. This should have no pending
- * connections, and should appear in the hash table. */
-#define CACHE_STATE_CACHED 2
-
-/** @name status values for a single DNS request.
- *
- * @{ */
-/** The DNS request is in progress. */
-#define RES_STATUS_INFLIGHT 1
-/** The DNS request finished and gave an answer */
-#define RES_STATUS_DONE_OK 2
-/** The DNS request finished and gave an error */
-#define RES_STATUS_DONE_ERR 3
-/**@}*/
-
-/** A DNS request: possibly completed, possibly pending; cached_resolve
- * structs are stored at the OR side in a hash table, and as a linked
- * list from oldest to newest.
- */
-typedef struct cached_resolve_t {
-  HT_ENTRY(cached_resolve_t) node;
-  uint32_t magic;  /**< Must be CACHED_RESOLVE_MAGIC */
-  char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */
-
-  union {
-    uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>, if successful.
-                         * (In host order.) */
-    int err_ipv4; /**< One of DNS_ERR_*, if IPv4 lookup failed. */
-  } result_ipv4; /**< Outcome of IPv4 lookup */
-  union {
-    struct in6_addr addr_ipv6; /**< IPv6 addr for <b>address</b>, if
-                                * successful */
-    int err_ipv6; /**< One of DNS_ERR_*, if IPv6 lookup failed. */
-  } result_ipv6; /**< Outcome of IPv6 lookup, if any */
-  union {
-    char *hostname; /** A hostname, if PTR lookup happened successfully*/
-    int err_hostname; /** One of DNS_ERR_*, if PTR lookup failed. */
-  } result_ptr;
-  /** @name Status fields
-   *
-   * These take one of the RES_STATUS_* values, depending on the state
-   * of the corresponding lookup.
-   *
-   * @{ */
-  unsigned int res_status_ipv4 : 2;
-  unsigned int res_status_ipv6 : 2;
-  unsigned int res_status_hostname : 2;
-  /**@}*/
-  uint8_t state; /**< Is this cached entry pending/done/informative? */
-
-  time_t expire; /**< Remove items from cache after this time. */
-  uint32_t ttl_ipv4; /**< What TTL did the nameserver tell us? */
-  uint32_t ttl_ipv6; /**< What TTL did the nameserver tell us? */
-  uint32_t ttl_hostname; /**< What TTL did the nameserver tell us? */
-  /** Connections that want to know when we get an answer for this resolve. */
-  pending_connection_t *pending_connections;
-  /** Position of this element in the heap*/
-  int minheap_idx;
-} cached_resolve_t;
-
 static void purge_expired_resolves(time_t now);
 static void dns_found_answer(const char *address, uint8_t query_type,
                              int dns_answer,
                              const tor_addr_t *addr,
                              const char *hostname,
                              uint32_t ttl);
-static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type,
-                               const cached_resolve_t *resolve);
 static int launch_resolve(cached_resolve_t *resolve);
 static void add_wildcarded_test_address(const char *address);
 static int configure_nameservers(int force);
 static int answer_is_wildcarded(const char *ip);
-static int dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
-                            or_circuit_t *oncirc, char **resolved_to_hostname,
-                            int *made_connection_pending_out,
-                            cached_resolve_t **resolve_out);
 static int set_exitconn_info_from_resolve(edge_connection_t *exitconn,
                                           const cached_resolve_t *resolve,
                                           char **hostname_out);
@@ -367,7 +278,7 @@ dns_clip_ttl(uint32_t ttl)
 
 /** Helper: Given a TTL from a DNS response, determine how long to hold it in
  * our cache. */
-static uint32_t
+STATIC uint32_t
 dns_get_expiry_ttl(uint32_t ttl)
 {
   if (ttl < MIN_DNS_TTL)
@@ -605,9 +516,9 @@ purge_expired_resolves(time_t now)
  * answer back along circ; otherwise, send the answer back along
  * <b>conn</b>'s attached circuit.
  */
-static void
-send_resolved_cell(edge_connection_t *conn, uint8_t answer_type,
-                   const cached_resolve_t *resolved)
+MOCK_IMPL(STATIC void,
+send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type,
+                    const cached_resolve_t *resolved))
 {
   char buf[RELAY_PAYLOAD_SIZE], *cp = buf;
   size_t buflen = 0;
@@ -671,8 +582,9 @@ send_resolved_cell(edge_connection_t *conn, uint8_t answer_type,
  * answer back along circ; otherwise, send the answer back along
  * <b>conn</b>'s attached circuit.
  */
-static void
-send_resolved_hostname_cell(edge_connection_t *conn, const char *hostname)
+MOCK_IMPL(STATIC void,
+send_resolved_hostname_cell,(edge_connection_t *conn,
+                             const char *hostname))
 {
   char buf[RELAY_PAYLOAD_SIZE];
   size_t buflen;
@@ -800,11 +712,11 @@ dns_resolve(edge_connection_t *exitconn)
  *
  * Set *<b>resolve_out</b> to a cached resolve, if we found one.
  */
-static int
-dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
+MOCK_IMPL(STATIC int,
+dns_resolve_impl,(edge_connection_t *exitconn, int is_resolve,
                  or_circuit_t *oncirc, char **hostname_out,
                  int *made_connection_pending_out,
-                 cached_resolve_t **resolve_out)
+                 cached_resolve_t **resolve_out))
 {
   cached_resolve_t *resolve;
   cached_resolve_t search;
@@ -1145,8 +1057,8 @@ connection_dns_remove(edge_connection_t *conn)
  * the resolve for <b>address</b> itself, and remove any cached results for
  * <b>address</b> from the cache.
  */
-void
-dns_cancel_pending_resolve(const char *address)
+MOCK_IMPL(void,
+dns_cancel_pending_resolve,(const char *address))
 {
   pending_connection_t *pend;
   cached_resolve_t search;

+ 17 - 1
src/or/dns.h

@@ -20,7 +20,7 @@ int dns_reset(void);
 void connection_dns_remove(edge_connection_t *conn);
 void assert_connection_edge_not_dns_pending(edge_connection_t *conn);
 void assert_all_pending_dns_resolves_ok(void);
-void dns_cancel_pending_resolve(const char *question);
+MOCK_DECL(void,dns_cancel_pending_resolve,(const char *question));
 int dns_resolve(edge_connection_t *exitconn);
 void dns_launch_correctness_checks(void);
 int dns_seems_to_be_broken(void);
@@ -28,5 +28,21 @@ int dns_seems_to_be_broken_for_ipv6(void);
 void dns_reset_correctness_checks(void);
 void dump_dns_mem_usage(int severity);
 
+#ifdef DNS_PRIVATE
+#include "dns_structs.h"
+
+STATIC uint32_t dns_get_expiry_ttl(uint32_t ttl);
+
+MOCK_DECL(STATIC int,dns_resolve_impl,(edge_connection_t *exitconn,
+int is_resolve,or_circuit_t *oncirc, char **hostname_out,
+int *made_connection_pending_out, cached_resolve_t **resolve_out));
+
+MOCK_DECL(STATIC void,send_resolved_cell,(edge_connection_t *conn,
+uint8_t answer_type,const cached_resolve_t *resolved));
+
+MOCK_DECL(STATIC void,send_resolved_hostname_cell,(edge_connection_t *conn,
+const char *hostname));
+#endif
+
 #endif
 

+ 90 - 0
src/or/dns_structs.h

@@ -0,0 +1,90 @@
+#ifndef TOR_DNS_STRUCTS_H
+#define TOR_DNS_STRUCTS_H
+
+/** Longest hostname we're willing to resolve. */
+#define MAX_ADDRESSLEN 256
+
+/** Linked list of connections waiting for a DNS answer. */
+typedef struct pending_connection_t {
+  edge_connection_t *conn;
+  struct pending_connection_t *next;
+} pending_connection_t;
+
+/** Value of 'magic' field for cached_resolve_t.  Used to try to catch bad
+ * pointers and memory stomping. */
+#define CACHED_RESOLVE_MAGIC 0x1234F00D
+
+/* Possible states for a cached resolve_t */
+/** We are waiting for the resolver system to tell us an answer here.
+ * When we get one, or when we time out, the state of this cached_resolve_t
+ * will become "DONE" and we'll possibly add a CACHED
+ * entry. This cached_resolve_t will be in the hash table so that we will
+ * know not to launch more requests for this addr, but rather to add more
+ * connections to the pending list for the addr. */
+#define CACHE_STATE_PENDING 0
+/** This used to be a pending cached_resolve_t, and we got an answer for it.
+ * Now we're waiting for this cached_resolve_t to expire.  This should
+ * have no pending connections, and should not appear in the hash table. */
+#define CACHE_STATE_DONE 1
+/** We are caching an answer for this address. This should have no pending
+ * connections, and should appear in the hash table. */
+#define CACHE_STATE_CACHED 2
+
+/** @name status values for a single DNS request.
+ *
+ * @{ */
+/** The DNS request is in progress. */
+#define RES_STATUS_INFLIGHT 1
+/** The DNS request finished and gave an answer */
+#define RES_STATUS_DONE_OK 2
+/** The DNS request finished and gave an error */
+#define RES_STATUS_DONE_ERR 3
+/**@}*/
+
+/** A DNS request: possibly completed, possibly pending; cached_resolve
+ * structs are stored at the OR side in a hash table, and as a linked
+ * list from oldest to newest.
+ */
+typedef struct cached_resolve_t {
+  HT_ENTRY(cached_resolve_t) node;
+  uint32_t magic;  /**< Must be CACHED_RESOLVE_MAGIC */
+  char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */
+
+  union {
+    uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>, if successful.
+                         * (In host order.) */
+    int err_ipv4; /**< One of DNS_ERR_*, if IPv4 lookup failed. */
+  } result_ipv4; /**< Outcome of IPv4 lookup */
+  union {
+    struct in6_addr addr_ipv6; /**< IPv6 addr for <b>address</b>, if
+                                * successful */
+    int err_ipv6; /**< One of DNS_ERR_*, if IPv6 lookup failed. */
+  } result_ipv6; /**< Outcome of IPv6 lookup, if any */
+  union {
+    char *hostname; /** A hostname, if PTR lookup happened successfully*/
+    int err_hostname; /** One of DNS_ERR_*, if PTR lookup failed. */
+  } result_ptr;
+  /** @name Status fields
+   *
+   * These take one of the RES_STATUS_* values, depending on the state
+   * of the corresponding lookup.
+   *
+   * @{ */
+  unsigned int res_status_ipv4 : 2;
+  unsigned int res_status_ipv6 : 2;
+  unsigned int res_status_hostname : 2;
+  /**@}*/
+  uint8_t state; /**< Is this cached entry pending/done/informative? */
+
+  time_t expire; /**< Remove items from cache after this time. */
+  uint32_t ttl_ipv4; /**< What TTL did the nameserver tell us? */
+  uint32_t ttl_ipv6; /**< What TTL did the nameserver tell us? */
+  uint32_t ttl_hostname; /**< What TTL did the nameserver tell us? */
+  /** Connections that want to know when we get an answer for this resolve. */
+  pending_connection_t *pending_connections;
+  /** Position of this element in the heap*/
+  int minheap_idx;
+} cached_resolve_t;
+
+#endif
+

+ 1 - 0
src/test/include.am

@@ -90,6 +90,7 @@ src_test_test_SOURCES = \
 	src/test/test_threads.c \
 	src/test/test_util.c \
 	src/test/test_helpers.c \
+        src/test/test_dns.c \
 	src/test/testing_common.c \
 	src/ext/tinytest.c
 

+ 2 - 0
src/test/test.c

@@ -1158,6 +1158,7 @@ extern struct testcase_t socks_tests[];
 extern struct testcase_t status_tests[];
 extern struct testcase_t thread_tests[];
 extern struct testcase_t util_tests[];
+extern struct testcase_t dns_tests[];
 
 struct testgroup_t testgroups[] = {
   { "", test_array },
@@ -1204,6 +1205,7 @@ struct testgroup_t testgroups[] = {
   { "util/", util_tests },
   { "util/logging/", logging_tests },
   { "util/thread/", thread_tests },
+  { "dns/", dns_tests },
   END_OF_GROUPS
 };
 

+ 304 - 0
src/test/test_dns.c

@@ -0,0 +1,304 @@
+#include "or.h"
+#include "test.h"
+
+#define DNS_PRIVATE
+
+#include "dns.h"
+#include "connection.h"
+
+static void
+test_dns_clip_ttl(void *arg)
+{
+  (void)arg;
+
+  uint32_t ttl_mid = MIN_DNS_TTL / 2 + MAX_DNS_TTL / 2;
+
+  tt_int_op(dns_clip_ttl(MIN_DNS_TTL - 1),==,MIN_DNS_TTL);
+  tt_int_op(dns_clip_ttl(ttl_mid),==,ttl_mid);
+  tt_int_op(dns_clip_ttl(MAX_DNS_TTL + 1),==,MAX_DNS_TTL);
+
+  done:
+  return;
+}
+
+static void
+test_dns_expiry_ttl(void *arg)
+{
+  (void)arg;
+
+  uint32_t ttl_mid = MIN_DNS_TTL / 2 + MAX_DNS_ENTRY_AGE / 2;
+
+  tt_int_op(dns_get_expiry_ttl(MIN_DNS_TTL - 1),==,MIN_DNS_TTL);
+  tt_int_op(dns_get_expiry_ttl(ttl_mid),==,ttl_mid);
+  tt_int_op(dns_get_expiry_ttl(MAX_DNS_ENTRY_AGE + 1),==,MAX_DNS_ENTRY_AGE);
+
+  done:
+  return;
+}
+
+static int resolve_retval = 0;
+static int resolve_made_conn_pending = 0;
+static char *resolved_name = NULL;
+static cached_resolve_t *cache_entry = NULL;
+
+static int n_fake_impl = 0;
+
+/** This will be our configurable substitute for <b>dns_resolve_impl</b> in
+ * dns.c. It will return <b>resolve_retval</b>,
+ * and set <b>resolve_made_conn_pending</b> to
+ * <b>made_connection_pending_out</b>. It will set <b>hostname_out</b>
+ * to a duplicate of <b>resolved_name</b> and it will set <b>resolve_out</b>
+ * to <b>cache_entry</b>. Lastly, it will increment <b>n_fake_impl</b< by
+ * 1.
+ */
+STATIC int
+dns_resolve_fake_impl(edge_connection_t *exitconn, int is_resolve,
+                      or_circuit_t *oncirc, char **hostname_out,
+                      int *made_connection_pending_out,
+                      cached_resolve_t **resolve_out)
+{
+  if (made_connection_pending_out)
+    *made_connection_pending_out = resolve_made_conn_pending;
+
+  if (hostname_out && resolved_name)
+    *hostname_out = tor_strdup(resolved_name);
+
+  if (resolve_out && cache_entry)
+    *resolve_out = cache_entry;
+
+  n_fake_impl++;
+
+  return resolve_retval;
+}
+
+static edge_connection_t *conn_for_resolved_cell = NULL;
+
+static int n_send_resolved_cell_replacement = 0;
+static uint8_t last_answer_type = 0;
+static cached_resolve_t *last_resolved;
+
+static void
+send_resolved_cell_replacement(edge_connection_t *conn, uint8_t answer_type,
+                               const cached_resolve_t *resolved)
+{
+  conn_for_resolved_cell = conn;
+  
+  last_answer_type = answer_type;
+  last_resolved = (cached_resolve_t *)resolved;
+
+  n_send_resolved_cell_replacement++;
+}
+
+static int n_send_resolved_hostname_cell_replacement = 0;
+
+static char *last_resolved_hostname = NULL;
+
+static void
+send_resolved_hostname_cell_replacement(edge_connection_t *conn,
+                                        const char *hostname)
+{
+  conn_for_resolved_cell = conn;
+
+  last_resolved_hostname = (char *)hostname;
+
+  n_send_resolved_hostname_cell_replacement++;
+}
+
+static int n_dns_cancel_pending_resolve_replacement = 0;
+
+static void
+dns_cancel_pending_resolve_replacement(const char *address)
+{
+  n_dns_cancel_pending_resolve_replacement++;
+}
+
+static int n_connection_free = 0;
+static connection_t *last_freed_conn = NULL;
+
+static void
+connection_free_replacement(connection_t *conn)
+{
+   n_connection_free++;
+
+   last_freed_conn = conn;
+}
+
+static void
+test_dns_resolve_outer(void *arg)
+{
+  int retval;
+  int prev_n_send_resolved_hostname_cell_replacement;
+  int prev_n_send_resolved_cell_replacement;
+  int prev_n_connection_free;
+  cached_resolve_t *fake_resolved = tor_malloc(sizeof(cached_resolve_t));
+  edge_connection_t *exitconn = tor_malloc(sizeof(edge_connection_t));
+  edge_connection_t *nextconn = tor_malloc(sizeof(edge_connection_t));
+
+  or_circuit_t *on_circuit = tor_malloc(sizeof(or_circuit_t));
+  memset(on_circuit,0,sizeof(or_circuit_t));
+  on_circuit->base_.magic = OR_CIRCUIT_MAGIC;
+
+  memset(fake_resolved,0,sizeof(cached_resolve_t));
+  memset(exitconn,0,sizeof(edge_connection_t));
+  memset(nextconn,0,sizeof(edge_connection_t));
+
+  MOCK(dns_resolve_impl,dns_resolve_fake_impl);
+  MOCK(send_resolved_cell,send_resolved_cell_replacement);
+  MOCK(send_resolved_hostname_cell,send_resolved_hostname_cell_replacement);
+
+  /*
+   * CASE 1: dns_resolve_impl returns 1 and sets a hostname. purpose is
+   * EXIT_PURPOSE_RESOLVE.
+   *
+   * We want dns_resolve() to call send_resolved_hostname_cell() for a
+   * given exit connection (represented by edge_connection_t object)
+   * with a hostname it received from _impl.
+   */ 
+
+  prev_n_send_resolved_hostname_cell_replacement = 
+  n_send_resolved_hostname_cell_replacement;
+
+  exitconn->base_.purpose = EXIT_PURPOSE_RESOLVE;
+  exitconn->on_circuit = &(on_circuit->base_);
+
+  resolve_retval = 1;
+  resolved_name = tor_strdup("www.torproject.org");
+
+  retval = dns_resolve(exitconn);
+  
+  tt_int_op(retval,==,1);
+  tt_str_op(resolved_name,==,last_resolved_hostname);
+  tt_assert(conn_for_resolved_cell == exitconn);
+  tt_int_op(n_send_resolved_hostname_cell_replacement,==,
+            prev_n_send_resolved_hostname_cell_replacement + 1);
+  tt_assert(exitconn->on_circuit == NULL);
+
+  last_resolved_hostname = NULL;
+
+  /* CASE 2: dns_resolve_impl returns 1, but does not set hostname.
+   * Instead, it yields cached_resolve_t object.
+   *
+   * We want dns_resolve to call send_resolved_cell on exitconn with 
+   * RESOLVED_TYPE_AUTO and the cached_resolve_t object from _impl.
+   */
+
+  tor_free(resolved_name);
+  resolved_name = NULL;
+
+  exitconn->on_circuit = &(on_circuit->base_);
+
+  cache_entry = fake_resolved;
+
+  prev_n_send_resolved_cell_replacement =
+  n_send_resolved_cell_replacement;
+
+  retval = dns_resolve(exitconn);
+
+  tt_int_op(retval,==,1);
+  tt_assert(conn_for_resolved_cell == exitconn);
+  tt_int_op(n_send_resolved_cell_replacement,==,
+            prev_n_send_resolved_cell_replacement + 1);
+  tt_assert(last_resolved == fake_resolved);
+  tt_int_op(last_answer_type,==,0xff);
+  tt_assert(exitconn->on_circuit == NULL);
+
+  /* CASE 3: The purpose of exit connection is not EXIT_PURPOSE_RESOLVE
+   * and _impl returns 1.
+   * 
+   * We want dns_resolve to prepend exitconn to n_streams linked list.
+   * We don't want it to send any cells about hostname being resolved.
+   */
+
+  exitconn->base_.purpose = EXIT_PURPOSE_CONNECT;
+  exitconn->on_circuit = &(on_circuit->base_);
+
+  on_circuit->n_streams = nextconn;
+
+  prev_n_send_resolved_cell_replacement =
+  n_send_resolved_cell_replacement;
+
+  prev_n_send_resolved_hostname_cell_replacement = 
+  n_send_resolved_hostname_cell_replacement;
+
+  retval = dns_resolve(exitconn);
+
+  tt_int_op(retval,==,1);
+  tt_assert(on_circuit->n_streams == exitconn);
+  tt_assert(exitconn->next_stream == nextconn);
+  tt_int_op(prev_n_send_resolved_cell_replacement,==,
+            n_send_resolved_cell_replacement);
+  tt_int_op(prev_n_send_resolved_hostname_cell_replacement,==,
+            n_send_resolved_hostname_cell_replacement);
+
+  /* CASE 4: _impl returns 0. 
+   * 
+   * We want dns_resolve() to set exitconn state to 
+   * EXIT_CONN_STATE_RESOLVING and prepend exitconn to resolving_streams
+   * linked list.
+   */ 
+
+  exitconn->on_circuit = &(on_circuit->base_);
+
+  resolve_retval = 0;
+
+  exitconn->next_stream = NULL;
+  on_circuit->resolving_streams = nextconn;
+
+  retval = dns_resolve(exitconn);
+
+  tt_int_op(retval,==,0);
+  tt_int_op(exitconn->base_.state,==,EXIT_CONN_STATE_RESOLVING);
+  tt_assert(on_circuit->resolving_streams == exitconn);
+  tt_assert(exitconn->next_stream == nextconn);
+
+  /* CASE 5: _impl returns -1 when purpose of exitconn is 
+   * EXIT_PURPOSE_RESOLVE. We want dns_resolve to call send_resolved_cell
+   * on exitconn with type being RESOLVED_TYPE_ERROR.
+   */ 
+
+  MOCK(dns_cancel_pending_resolve,dns_cancel_pending_resolve_replacement); 
+  MOCK(connection_free,connection_free_replacement);
+
+  exitconn->on_circuit = &(on_circuit->base_);
+  exitconn->base_.purpose = EXIT_PURPOSE_RESOLVE;
+
+  resolve_retval = -1;
+
+  prev_n_send_resolved_cell_replacement =
+  n_send_resolved_cell_replacement;
+
+  prev_n_connection_free = n_connection_free;
+
+  retval = dns_resolve(exitconn);
+
+  tt_int_op(retval,==,-1);
+  tt_int_op(n_send_resolved_cell_replacement,==,
+            prev_n_send_resolved_cell_replacement + 1);
+  tt_int_op(last_answer_type,==,RESOLVED_TYPE_ERROR);
+  tt_int_op(n_dns_cancel_pending_resolve_replacement,==,1);
+  tt_int_op(n_connection_free,==,prev_n_connection_free + 1);
+  tt_assert(last_freed_conn == TO_CONN(exitconn));
+
+  done:
+  UNMOCK(dns_resolve_impl);
+  UNMOCK(send_resolved_cell);
+  UNMOCK(send_resolved_hostname_cell);
+  UNMOCK(dns_cancel_pending_resolve);
+  UNMOCK(connection_free);
+  tor_free(on_circuit);
+  tor_free(exitconn);
+  tor_free(nextconn);
+  tor_free(resolved_name);
+  tor_free(fake_resolved);
+  return;
+}
+
+struct testcase_t dns_tests[] = {
+   { "clip_ttl", test_dns_clip_ttl, 0, NULL, NULL },
+   { "expiry_ttl", test_dns_expiry_ttl, 0, NULL, NULL },
+   { "resolve_outer", test_dns_resolve_outer, TT_FORK, NULL, NULL },
+   END_OF_TESTCASES
+};
+
+#undef DNS_PRIVATE
+