aboutsummaryrefslogtreecommitdiff
path: root/src/or/dns.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/or/dns.c')
-rw-r--r--src/or/dns.c339
1 files changed, 123 insertions, 216 deletions
diff --git a/src/or/dns.c b/src/or/dns.c
index b55bf7384e..c1e3c3256e 100644
--- a/src/or/dns.c
+++ b/src/or/dns.c
@@ -1,6 +1,6 @@
/* Copyright (c) 2003-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
- * Copyright (c) 2007-2013, The Tor Project, Inc. */
+ * Copyright (c) 2007-2016, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/**
@@ -9,8 +9,46 @@
* This is implemented as a wrapper around Adam Langley's eventdns.c code.
* (We can't just use gethostbyname() and friends because we really need to
* be nonblocking.)
+ *
+ * There are three main cases when a Tor relay uses dns.c to launch a DNS
+ * request:
+ * <ol>
+ * <li>To check whether the DNS server is working more or less correctly.
+ * This happens via dns_launch_correctness_checks(). The answer is
+ * reported in the return value from later calls to
+ * dns_seems_to_be_broken().
+ * <li>When a client has asked the relay, in a RELAY_BEGIN cell, to connect
+ * to a given server by hostname. This happens via dns_resolve().
+ * <li>When a client has asked the rela, in a RELAY_RESOLVE cell, to look
+ * up a given server's IP address(es) by hostname. This also happens via
+ * dns_resolve().
+ * </ol>
+ *
+ * Each of these gets handled a little differently.
+ *
+ * To check for correctness, we look up some hostname we expect to exist and
+ * have real entries, some hostnames which we expect to definitely not exist,
+ * and some hostnames that we expect to probably not exist. If too many of
+ * the hostnames that shouldn't exist do exist, that's a DNS hijacking
+ * attempt. If too many of the hostnames that should exist have the same
+ * addresses as the ones that shouldn't exist, that's a very bad DNS hijacking
+ * attempt, or a very naughty captive portal. And if the hostnames that
+ * should exist simply don't exist, we probably have a broken nameserver.
+ *
+ * To handle client requests, we first check our cache for answers. If there
+ * isn't something up-to-date, we've got to launch A or AAAA requests as
+ * appropriate. How we handle responses to those in particular is a bit
+ * complex; see dns_lookup() and set_exitconn_info_from_resolve().
+ *
+ * When a lookup is finally complete, the inform_pending_connections()
+ * function will tell all of the streams that have been waiting for the
+ * resolve, by calling connection_exit_connect() if the client sent a
+ * RELAY_BEGIN cell, and by calling send_resolved_cell() or
+ * send_hostname_cell() if the client sent a RELAY_RESOLVE cell.
**/
+#define DNS_PRIVATE
+
#include "or.h"
#include "circuitlist.h"
#include "circuituse.h"
@@ -24,65 +62,9 @@
#include "relay.h"
#include "router.h"
#include "ht.h"
-#include "../common/sandbox.h"
-#ifdef HAVE_EVENT2_DNS_H
+#include "sandbox.h"
#include <event2/event.h>
#include <event2/dns.h>
-#else
-#include <event.h>
-#include "eventdns.h"
-#ifndef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
-#define HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
-#endif
-#endif
-
-#ifndef HAVE_EVENT2_DNS_H
-struct evdns_base;
-struct evdns_request;
-#define evdns_base_new(x,y) tor_malloc(1)
-#define evdns_base_clear_nameservers_and_suspend(base) \
- evdns_clear_nameservers_and_suspend()
-#define evdns_base_search_clear(base) evdns_search_clear()
-#define evdns_base_set_default_outgoing_bind_address(base, a, len) \
- evdns_set_default_outgoing_bind_address((a),(len))
-#define evdns_base_resolv_conf_parse(base, options, fname) \
- evdns_resolv_conf_parse((options), (fname))
-#define evdns_base_count_nameservers(base) \
- evdns_count_nameservers()
-#define evdns_base_resume(base) \
- evdns_resume()
-#define evdns_base_config_windows_nameservers(base) \
- evdns_config_windows_nameservers()
-#define evdns_base_set_option_(base, opt, val) \
- evdns_set_option((opt),(val),DNS_OPTIONS_ALL)
-/* Note: our internal eventdns.c, plus Libevent 1.4, used a 1 return to
- * signify failure to launch a resolve. Libevent 2.0 uses a -1 return to
- * signify a failure on a resolve, though if we're on Libevent 2.0, we should
- * have event2/dns.h and never hit these macros. Regardless, 0 is success. */
-#define evdns_base_resolve_ipv4(base, addr, options, cb, ptr) \
- ((evdns_resolve_ipv4((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_ipv6(base, addr, options, cb, ptr) \
- ((evdns_resolve_ipv6((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_reverse(base, addr, options, cb, ptr) \
- ((evdns_resolve_reverse((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-#define evdns_base_resolve_reverse_ipv6(base, addr, options, cb, ptr) \
- ((evdns_resolve_reverse_ipv6((addr), (options), (cb), (ptr))!=0) \
- ? NULL : ((void*)1))
-
-#elif defined(LIBEVENT_VERSION_NUMBER) && LIBEVENT_VERSION_NUMBER < 0x02000303
-#define evdns_base_set_option_(base, opt, val) \
- evdns_base_set_option((base), (opt),(val),DNS_OPTIONS_ALL)
-
-#else
-#define evdns_base_set_option_ evdns_base_set_option
-
-#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? */
@@ -102,107 +84,15 @@ 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);
static int evdns_err_is_transient(int err);
static void inform_pending_connections(cached_resolve_t *resolve);
static void make_pending_resolve_cached(cached_resolve_t *cached);
@@ -227,7 +117,7 @@ static int dns_is_broken_for_ipv6 = 0;
/** Function to compare hashed resolves on their addresses; used to
* implement hash tables. */
-static INLINE int
+static inline int
cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b)
{
/* make this smarter one day? */
@@ -236,7 +126,7 @@ cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b)
}
/** Hash function for cached_resolve objects */
-static INLINE unsigned int
+static inline unsigned int
cached_resolve_hash(cached_resolve_t *a)
{
return (unsigned) siphash24g((const uint8_t*)a->address, strlen(a->address));
@@ -244,8 +134,8 @@ cached_resolve_hash(cached_resolve_t *a)
HT_PROTOTYPE(cache_map, cached_resolve_t, node, cached_resolve_hash,
cached_resolves_eq)
-HT_GENERATE(cache_map, cached_resolve_t, node, cached_resolve_hash,
- cached_resolves_eq, 0.6, malloc, realloc, free)
+HT_GENERATE2(cache_map, cached_resolve_t, node, cached_resolve_hash,
+ cached_resolves_eq, 0.6, tor_reallocarray_, tor_free_)
/** Initialize the DNS cache. */
static void
@@ -353,29 +243,19 @@ has_dns_init_failed(void)
}
/** Helper: Given a TTL from a DNS response, determine what TTL to give the
- * OP that asked us to resolve it. */
+ * OP that asked us to resolve it, and how long to cache that record
+ * ourselves. */
uint32_t
dns_clip_ttl(uint32_t ttl)
{
- if (ttl < MIN_DNS_TTL)
- return MIN_DNS_TTL;
- else if (ttl > MAX_DNS_TTL)
- return MAX_DNS_TTL;
- else
- return ttl;
-}
-
-/** Helper: Given a TTL from a DNS response, determine how long to hold it in
- * our cache. */
-static uint32_t
-dns_get_expiry_ttl(uint32_t ttl)
-{
- if (ttl < MIN_DNS_TTL)
- return MIN_DNS_TTL;
- else if (ttl > MAX_DNS_ENTRY_AGE)
- return MAX_DNS_ENTRY_AGE;
+ /* This logic is a defense against "DefectTor" DNS-based traffic
+ * confirmation attacks, as in https://nymity.ch/tor-dns/tor-dns.pdf .
+ * We only give two values: a "low" value and a "high" value.
+ */
+ if (ttl < MIN_DNS_TTL_AT_EXIT)
+ return MIN_DNS_TTL_AT_EXIT;
else
- return ttl;
+ return MAX_DNS_TTL_AT_EXIT;
}
/** Helper: free storage held by an entry in the DNS cache. */
@@ -446,7 +326,7 @@ cached_resolve_add_answer(cached_resolve_t *resolve,
resolve->result_ipv4.err_ipv4 = dns_result;
resolve->res_status_ipv4 = RES_STATUS_DONE_ERR;
}
-
+ resolve->ttl_ipv4 = ttl;
} else if (query_type == DNS_IPv6_AAAA) {
if (resolve->res_status_ipv6 != RES_STATUS_INFLIGHT)
return;
@@ -461,6 +341,7 @@ cached_resolve_add_answer(cached_resolve_t *resolve,
resolve->result_ipv6.err_ipv6 = dns_result;
resolve->res_status_ipv6 = RES_STATUS_DONE_ERR;
}
+ resolve->ttl_ipv6 = ttl;
}
}
@@ -605,9 +486,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;
@@ -641,6 +522,7 @@ send_resolved_cell(edge_connection_t *conn, uint8_t answer_type,
answer_type = RESOLVED_TYPE_ERROR;
/* fall through. */
}
+ /* Falls through. */
case RESOLVED_TYPE_ERROR_TRANSIENT:
case RESOLVED_TYPE_ERROR:
{
@@ -671,8 +553,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 +683,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;
@@ -938,8 +821,14 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
}
/** Given an exit connection <b>exitconn</b>, and a cached_resolve_t
- * <b>resolve</b> whose DNS lookups have all succeeded or failed, update the
- * appropriate fields (address_ttl and addr) of <b>exitconn</b>.
+ * <b>resolve</b> whose DNS lookups have all either succeeded or failed,
+ * update the appropriate fields (address_ttl and addr) of <b>exitconn</b>.
+ *
+ * The logic can be complicated here, since we might have launched both
+ * an A lookup and an AAAA lookup, and since either of those might have
+ * succeeded or failed, and since we want to answer a RESOLVE cell with
+ * a full answer but answer a BEGIN cell with whatever answer the client
+ * would accept <i>and</i> we could still connect to.
*
* If this is a reverse lookup, set *<b>hostname_out</b> to a newly allocated
* copy of the name resulting hostname.
@@ -947,10 +836,10 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
* Return -2 on a transient error, -1 on a permenent error, and 1 on
* a successful lookup.
*/
-static int
-set_exitconn_info_from_resolve(edge_connection_t *exitconn,
- const cached_resolve_t *resolve,
- char **hostname_out)
+MOCK_IMPL(STATIC int,
+set_exitconn_info_from_resolve,(edge_connection_t *exitconn,
+ const cached_resolve_t *resolve,
+ char **hostname_out))
{
int ipv4_ok, ipv6_ok, answer_with_ipv4, r;
uint32_t begincell_flags;
@@ -1145,8 +1034,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;
@@ -1218,7 +1107,7 @@ dns_cancel_pending_resolve(const char *address)
/** Return true iff <b>address</b> is one of the addresses we use to verify
* that well-known sites aren't being hijacked by our DNS servers. */
-static INLINE int
+static inline int
is_test_address(const char *address)
{
const or_options_t *options = get_options();
@@ -1282,7 +1171,12 @@ dns_found_answer(const char *address, uint8_t query_type,
/** Given a pending cached_resolve_t that we just finished resolving,
* inform every connection that was waiting for the outcome of that
- * resolution. */
+ * resolution.
+ *
+ * Do this by sending a RELAY_RESOLVED cell (if the pending stream had sent us
+ * RELAY_RESOLVE cell), or by launching an exit connection (if the pending
+ * stream had send us a RELAY_BEGIN cell).
+ */
static void
inform_pending_connections(cached_resolve_t *resolve)
{
@@ -1415,7 +1309,7 @@ make_pending_resolve_cached(cached_resolve_t *resolve)
resolve->ttl_hostname < ttl)
ttl = resolve->ttl_hostname;
- set_expiry(new_resolve, time(NULL) + dns_get_expiry_ttl(ttl));
+ set_expiry(new_resolve, time(NULL) + dns_clip_ttl(ttl));
}
assert_cache_ok();
@@ -1465,23 +1359,6 @@ configure_nameservers(int force)
}
}
-#ifdef HAVE_EVDNS_SET_DEFAULT_OUTGOING_BIND_ADDRESS
- if (! tor_addr_is_null(&options->OutboundBindAddressIPv4_)) {
- int socklen;
- struct sockaddr_storage ss;
- socklen = tor_addr_to_sockaddr(&options->OutboundBindAddressIPv4_, 0,
- (struct sockaddr *)&ss, sizeof(ss));
- if (socklen <= 0) {
- log_warn(LD_BUG, "Couldn't convert outbound bind address to sockaddr."
- " Ignoring.");
- } else {
- evdns_base_set_default_outgoing_bind_address(the_evdns_base,
- (struct sockaddr *)&ss,
- socklen);
- }
- }
-#endif
-
evdns_set_log_fn(evdns_log_cb);
if (conf_fname) {
log_debug(LD_FS, "stat()ing %s", conf_fname);
@@ -1546,16 +1423,33 @@ configure_nameservers(int force)
}
#endif
-#define SET(k,v) evdns_base_set_option_(the_evdns_base, (k), (v))
+#define SET(k,v) evdns_base_set_option(the_evdns_base, (k), (v))
+ // If we only have one nameserver, it does not make sense to back off
+ // from it for a timeout. Unfortunately, the value for max-timeouts is
+ // currently clamped by libevent to 255, but it does not hurt to set
+ // it higher in case libevent gets a patch for this.
+ // Reducing attempts in the case of just one name server too, because
+ // it is very likely to be a local one where a network connectivity
+ // issue should not cause an attempt to fail.
if (evdns_base_count_nameservers(the_evdns_base) == 1) {
- SET("max-timeouts:", "16");
- SET("timeout:", "10");
+ SET("max-timeouts:", "1000000");
+ SET("attempts:", "1");
} else {
SET("max-timeouts:", "3");
- SET("timeout:", "5");
}
+ // Elongate the queue of maximum inflight dns requests, so if a bunch
+ // time out at the resolver (happens commonly with unbound) we won't
+ // stall every other DNS request. This potentially means some wasted
+ // CPU as there's a walk over a linear queue involved, but this is a
+ // much better tradeoff compared to just failing DNS requests because
+ // of a full queue.
+ SET("max-inflight:", "8192");
+
+ // Time out after 5 seconds if no reply.
+ SET("timeout:", "5");
+
if (options->ServerDNSRandomizeCase)
SET("randomize-case:", "1");
else
@@ -1752,8 +1646,8 @@ launch_one_resolve(const char *address, uint8_t query_type,
/** For eventdns: start resolving as necessary to find the target for
* <b>exitconn</b>. Returns -1 on error, -2 on transient error,
* 0 on "resolve launched." */
-static int
-launch_resolve(cached_resolve_t *resolve)
+MOCK_IMPL(STATIC int,
+launch_resolve,(cached_resolve_t *resolve))
{
tor_addr_t a;
int r;
@@ -2206,5 +2100,18 @@ assert_cache_ok_(void)
}
});
}
+
#endif
+cached_resolve_t
+*dns_get_cache_entry(cached_resolve_t *query)
+{
+ return HT_FIND(cache_map, &cache_root, query);
+}
+
+void
+dns_insert_cache_entry(cached_resolve_t *new_entry)
+{
+ HT_INSERT(cache_map, &cache_root, new_entry);
+}
+