Explorar el Código

Make DNS resolve requests work for IPv6

* If there's an IPv4 and an IPv6 address, return both in the resolved
  cell.
* Treat all resolve requests as permitting IPv6, since by the spec they're
  allowed to, and by the code that won't break anything.
Nick Mathewson hace 11 años
padre
commit
0f899518cf
Se han modificado 1 ficheros con 63 adiciones y 42 borrados
  1. 63 42
      src/or/dns.c

+ 63 - 42
src/or/dns.c

@@ -148,7 +148,8 @@ typedef struct cached_resolve_t {
   char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */
 
   union {
-    uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>, if successful */
+    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 {
@@ -188,14 +189,16 @@ static void dns_found_answer(const char *address, uint8_t query_type,
                              const tor_addr_t *addr,
                              const char *hostname,
                              uint32_t ttl);
-static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type);
+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);
+                            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);
@@ -593,55 +596,55 @@ purge_expired_resolves(time_t now)
 
 /** Send a response to the RESOLVE request of a connection.
  * <b>answer_type</b> must be one of
- * RESOLVED_TYPE_(IPV4|IPV6|ERROR|ERROR_TRANSIENT|AUTO).
+ * RESOLVED_TYPE_(AUTO|ERROR|ERROR_TRANSIENT|).
  *
  * If <b>circ</b> is provided, and we have a cached answer, send the
  * 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)
+send_resolved_cell(edge_connection_t *conn, uint8_t answer_type,
+                   const cached_resolve_t *resolved)
 {
-  char buf[RELAY_PAYLOAD_SIZE];
-  size_t buflen;
+  char buf[RELAY_PAYLOAD_SIZE], *cp = buf;
+  size_t buflen = 0;
   uint32_t ttl;
 
-  if (answer_type == RESOLVED_TYPE_AUTO) {
-    sa_family_t family = tor_addr_family(&conn->base_.addr);
-    if (family == AF_INET)
-      answer_type = RESOLVED_TYPE_IPV4;
-    else if (family == AF_INET6)
-      answer_type = RESOLVED_TYPE_IPV6;
-    else
-      answer_type = RESOLVED_TYPE_ERROR_TRANSIENT;
-  }
-
   buf[0] = answer_type;
   ttl = dns_clip_ttl(conn->address_ttl);
 
   switch (answer_type)
     {
-    case RESOLVED_TYPE_IPV4:
-      buf[1] = 4;
-      set_uint32(buf+2, tor_addr_to_ipv4n(&conn->base_.addr));
-      set_uint32(buf+6, htonl(ttl));
-      buflen = 10;
-      break;
-    case RESOLVED_TYPE_IPV6:
-      {
-        const uint8_t *bytes = tor_addr_to_in6_addr8(&conn->base_.addr);
-        buf[1] = 16;
-        memcpy(buf+2, bytes, 16);
-        set_uint32(buf+18, htonl(ttl));
-        buflen = 22;
+    case RESOLVED_TYPE_AUTO:
+      if (resolved && resolved->res_status_ipv4 == RES_STATUS_DONE_OK) {
+        cp[0] = RESOLVED_TYPE_IPV4;
+        cp[1] = 4;
+        set_uint32(cp+2, htonl(resolved->result_ipv4.addr_ipv4));
+        set_uint32(cp+6, htonl(ttl));
+        cp += 10;
+      }
+      if (resolved && resolved->res_status_ipv6 == RES_STATUS_DONE_OK) {
+        const uint8_t *bytes = resolved->result_ipv6.addr_ipv6.s6_addr;
+        cp[0] = RESOLVED_TYPE_IPV6;
+        cp[1] = 16;
+        memcpy(cp+2, bytes, 16);
+        set_uint32(cp+18, htonl(ttl));
+        cp += 22;
+      }
+      if (cp != buf) {
+        buflen = cp - buf;
+        break;
+      } else {
+        answer_type = RESOLVED_TYPE_ERROR;
+        /* fall through. */
       }
-      break;
     case RESOLVED_TYPE_ERROR_TRANSIENT:
     case RESOLVED_TYPE_ERROR:
       {
         const char *errmsg = "Error resolving hostname";
         size_t msglen = strlen(errmsg);
 
+        buf[0] = answer_type;
         buf[1] = msglen;
         strlcpy(buf+2, errmsg, sizeof(buf)-2);
         set_uint32(buf+2+msglen, htonl(ttl));
@@ -719,10 +722,11 @@ dns_resolve(edge_connection_t *exitconn)
   int is_resolve, r;
   int made_connection_pending = 0;
   char *hostname = NULL;
+  cached_resolve_t *resolve = NULL;
   is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE;
 
   r = dns_resolve_impl(exitconn, is_resolve, oncirc, &hostname,
-                       &made_connection_pending);
+                       &made_connection_pending, &resolve);
 
   switch (r) {
     case 1:
@@ -733,7 +737,7 @@ dns_resolve(edge_connection_t *exitconn)
         if (hostname)
           send_resolved_hostname_cell(exitconn, hostname);
         else
-          send_resolved_cell(exitconn, RESOLVED_TYPE_AUTO);
+          send_resolved_cell(exitconn, RESOLVED_TYPE_AUTO, resolve);
         exitconn->on_circuit = NULL;
       } else {
         /* Add to the n_streams list; the calling function will send back a
@@ -755,7 +759,8 @@ dns_resolve(edge_connection_t *exitconn)
        * and stop everybody waiting for the same connection. */
       if (is_resolve) {
         send_resolved_cell(exitconn,
-             (r == -1) ? RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT);
+             (r == -1) ? RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT,
+             NULL);
       }
 
       exitconn->on_circuit = NULL;
@@ -789,11 +794,14 @@ dns_resolve(edge_connection_t *exitconn)
  * Set *<b>made_connection_pending_out</b> to true if we have placed
  * <b>exitconn</b> on the list of pending connections for some resolve; set it
  * to false otherwise.
+ *
+ * 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,
                  or_circuit_t *oncirc, char **hostname_out,
-                 int *made_connection_pending_out)
+                 int *made_connection_pending_out,
+                 cached_resolve_t **resolve_out)
 {
   cached_resolve_t *resolve;
   cached_resolve_t search;
@@ -891,6 +899,8 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
                   exitconn->base_.s,
                   escaped_safe_str(resolve->address));
 
+        *resolve_out = resolve;
+
         return set_exitconn_info_from_resolve(exitconn, resolve, hostname_out);
 
       case CACHE_STATE_DONE:
@@ -940,6 +950,8 @@ set_exitconn_info_from_resolve(edge_connection_t *exitconn,
                                char **hostname_out)
 {
   int ipv4_ok, ipv6_ok, answer_with_ipv4, r;
+  uint32_t begincell_flags;
+  const int is_resolve = exitconn->base_.purpose == EXIT_PURPOSE_RESOLVE;
   tor_assert(exitconn);
   tor_assert(resolve);
 
@@ -955,14 +967,22 @@ set_exitconn_info_from_resolve(edge_connection_t *exitconn,
 
   /* If we're here then the connection wants one or either of ipv4, ipv6, and
    * we can give it one or both. */
+  if (is_resolve) {
+    begincell_flags = BEGIN_FLAG_IPV6_OK;
+  } else {
+    begincell_flags = exitconn->begincell_flags;
+  }
+
   ipv4_ok = (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) &&
-    ! (exitconn->begincell_flags & BEGIN_FLAG_IPV4_NOT_OK);
+    ! (begincell_flags & BEGIN_FLAG_IPV4_NOT_OK);
   ipv6_ok = (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) &&
-    (exitconn->begincell_flags & BEGIN_FLAG_IPV6_OK) &&
+    (begincell_flags & BEGIN_FLAG_IPV6_OK) &&
     get_options()->IPv6Exit;
 
   /* Now decide which one to actually give. */
-  if (ipv4_ok && ipv6_ok) {
+  if (ipv4_ok && ipv6_ok && is_resolve) {
+    answer_with_ipv4 = 1;
+  } else if (ipv4_ok && ipv6_ok) {
     /* If we have both, see if our exit policy has an opinion. */
     const uint16_t port = exitconn->base_.port;
     int ipv4_allowed, ipv6_allowed;
@@ -978,7 +998,7 @@ set_exitconn_info_from_resolve(edge_connection_t *exitconn,
     } else {
       /* Our exit policy would permit both.  Answer with whichever the user
        * prefers */
-      answer_with_ipv4 = !(exitconn->begincell_flags &
+      answer_with_ipv4 = !(begincell_flags &
                            BEGIN_FLAG_IPV6_PREFERRED);
     }
   } else {
@@ -989,7 +1009,7 @@ set_exitconn_info_from_resolve(edge_connection_t *exitconn,
       answer_with_ipv4 = 0;
     } else {
       /* Neither one was okay. Choose based on user preference. */
-      answer_with_ipv4 = !(exitconn->begincell_flags &
+      answer_with_ipv4 = !(begincell_flags &
                            BEGIN_FLAG_IPV6_PREFERRED);
     }
   }
@@ -1292,7 +1312,8 @@ inform_pending_connections(cached_resolve_t *resolve)
         circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn);
       } else {
         send_resolved_cell(pendconn, r == -1 ?
-                          RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT);
+                         RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT,
+                         NULL);
         /* This detach must happen after we send the resolved cell. */
         circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn);
       }
@@ -1321,7 +1342,7 @@ inform_pending_connections(cached_resolve_t *resolve)
         if (pendconn->is_reverse_dns_lookup)
           send_resolved_hostname_cell(pendconn, hostname);
         else
-          send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO);
+          send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO, resolve);
         circ = circuit_get_by_edge_conn(pendconn);
         tor_assert(circ);
         circuit_detach_stream(circ, pendconn);