Revise the DNS subsystem to handle IPv6 exits.

Now, every cached_resolve_t can remember an IPv4 result *and* an IPv6
result.  As a light protection against timing-based distinguishers for
IPv6 users (and against complexity!), every forward request generates
an IPv4 *and* an IPv6 request, assuming that we're an IPv6 exit.  Once
we have answers or errors for both, we act accordingly.

This patch additionally makes some useful refactorings in the dns.c
code, though there is quite a bit more of useful refactoring that could
be done.

Additionally, have a new interface for the argument passed to the
evdns_callback function.  Previously, it was just the original address
we were resolving.  But it turns out that, on error, evdns doesn't
tell you the type of the query, so on a failure we didn't know whether
IPv4 or IPv6 queries were failing.

The new convention is to have the first byte of that argument include
the query type.  I've refactored the code a bit to make that simpler.
This commit is contained in:
Nick Mathewson 2012-11-05 13:26:29 -05:00
parent a58e17bcc3
commit 2889bd2642
3 changed files with 385 additions and 161 deletions

View file

@ -91,12 +91,13 @@ int connection_edge_update_circuit_isolation(const entry_connection_t *conn,
int dry_run); int dry_run);
void circuit_clear_isolation(origin_circuit_t *circ); void circuit_clear_isolation(origin_circuit_t *circ);
#ifdef CONNECTION_EDGE_PRIVATE /* DOCDOC*/
#define BEGIN_FLAG_IPV6_OK (1u<<0) #define BEGIN_FLAG_IPV6_OK (1u<<0)
#define BEGIN_FLAG_IPV4_NOT_OK (1u<<1) #define BEGIN_FLAG_IPV4_NOT_OK (1u<<1)
#define BEGIN_FLAG_IPV6_PREFERRED (1u<<2) #define BEGIN_FLAG_IPV6_PREFERRED (1u<<2)
#ifdef CONNECTION_EDGE_PRIVATE
/*DOCDOC*/ /*DOCDOC*/
typedef struct begin_cell_t { typedef struct begin_cell_t {
char *address; char *address;

View file

@ -117,7 +117,7 @@ typedef struct pending_connection_t {
/* Possible states for a cached resolve_t */ /* Possible states for a cached resolve_t */
/** We are waiting for the resolver system to tell us an answer here. /** 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 * 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_VALID or a CACHED_FAILED * 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 * 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 * know not to launch more requests for this addr, but rather to add more
* connections to the pending list for the addr. */ * connections to the pending list for the addr. */
@ -128,10 +128,12 @@ typedef struct pending_connection_t {
#define CACHE_STATE_DONE 1 #define CACHE_STATE_DONE 1
/** We are caching an answer for this address. This should have no pending /** We are caching an answer for this address. This should have no pending
* connections, and should appear in the hash table. */ * connections, and should appear in the hash table. */
#define CACHE_STATE_CACHED_VALID 2 #define CACHE_STATE_CACHED 2
/** We are caching a failure for this address. This should have no pending
* connections, and should appear in the hash table */ /* DOCDOC */
#define CACHE_STATE_CACHED_FAILED 3 #define RES_STATUS_INFLIGHT 1
#define RES_STATUS_DONE_OK 2
#define RES_STATUS_DONE_ERR 3
/** A DNS request: possibly completed, possibly pending; cached_resolve /** A DNS request: possibly completed, possibly pending; cached_resolve
* structs are stored at the OR side in a hash table, and as a linked * structs are stored at the OR side in a hash table, and as a linked
@ -141,17 +143,29 @@ typedef struct cached_resolve_t {
HT_ENTRY(cached_resolve_t) node; HT_ENTRY(cached_resolve_t) node;
uint32_t magic; uint32_t magic;
char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */ char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */
union { union {
struct { uint32_t addr_ipv4; /**< IPv4 addr for <b>address</b>. */
struct in6_addr addr6; /**< IPv6 addr for <b>address</b>. */ int err_ipv4;
uint32_t addr; /**< IPv4 addr for <b>address</b>. */ } result_ipv4;
} a; union {
char *hostname; /**< Hostname for <b>address</b> (if a reverse lookup) */ struct in6_addr addr_ipv6;
} result; int err_ipv6;
uint8_t state; /**< Is this cached entry pending/done/valid/failed? */ } result_ipv6;
uint8_t is_reverse; /**< Is this a reverse (addr-to-hostname) lookup? */ union {
char *hostname;
int err_hostname;
} result_ptr;
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? */
//uint8_t is_reverse; /**< Is this a reverse (addr-to-hostname) lookup? */
time_t expire; /**< Remove items from cache after this time. */ time_t expire; /**< Remove items from cache after this time. */
uint32_t ttl; /**< What TTL did the nameserver tell us? */ 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. */ /** Connections that want to know when we get an answer for this resolve. */
pending_connection_t *pending_connections; pending_connection_t *pending_connections;
/** Position of this element in the heap*/ /** Position of this element in the heap*/
@ -159,18 +173,26 @@ typedef struct cached_resolve_t {
} cached_resolve_t; } cached_resolve_t;
static void purge_expired_resolves(time_t now); static void purge_expired_resolves(time_t now);
static void dns_found_answer(const char *address, uint8_t is_reverse, static void dns_found_answer(const char *address, uint8_t query_type,
uint32_t addr, int dns_answer,
const char *hostname, char outcome, const tor_addr_t *addr,
const char *hostname,
uint32_t ttl); 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);
static int launch_resolve(edge_connection_t *exitconn); static int launch_resolve(cached_resolve_t *resolve);
static void add_wildcarded_test_address(const char *address); static void add_wildcarded_test_address(const char *address);
static int configure_nameservers(int force); static int configure_nameservers(int force);
static int answer_is_wildcarded(const char *ip); static int answer_is_wildcarded(const char *ip);
static int dns_resolve_impl(edge_connection_t *exitconn, int is_resolve, static int dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
or_circuit_t *oncirc, char **resolved_to_hostname, or_circuit_t *oncirc, char **resolved_to_hostname,
int *made_connection_pending_out); int *made_connection_pending_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);
#ifdef DEBUG_DNS_CACHE #ifdef DEBUG_DNS_CACHE
static void assert_cache_ok_(void); static void assert_cache_ok_(void);
#define assert_cache_ok() assert_cache_ok_() #define assert_cache_ok() assert_cache_ok_()
@ -182,6 +204,11 @@ static void assert_resolve_ok(cached_resolve_t *resolve);
/** Hash table of cached_resolve objects. */ /** Hash table of cached_resolve objects. */
static HT_HEAD(cache_map, cached_resolve_t) cache_root; static HT_HEAD(cache_map, cached_resolve_t) cache_root;
/*DOCDOC*/
static uint64_t n_ipv6_requests_made = 0;
static uint64_t n_ipv6_timeouts = 0;
static int dns_is_broken_for_ipv6 = 0;
/** Function to compare hashed resolves on their addresses; used to /** Function to compare hashed resolves on their addresses; used to
* implement hash tables. */ * implement hash tables. */
static INLINE int static INLINE int
@ -346,8 +373,8 @@ free_cached_resolve_(cached_resolve_t *r)
r->pending_connections = victim->next; r->pending_connections = victim->next;
tor_free(victim); tor_free(victim);
} }
if (r->is_reverse) if (r->res_status_hostname == RES_STATUS_DONE_OK)
tor_free(r->result.hostname); tor_free(r->result_ptr.hostname);
r->magic = 0xFF00FF00; r->magic = 0xFF00FF00;
tor_free(r); tor_free(r);
} }
@ -371,6 +398,65 @@ compare_cached_resolves_by_expiry_(const void *_a, const void *_b)
* will expire. */ * will expire. */
static smartlist_t *cached_resolve_pqueue = NULL; static smartlist_t *cached_resolve_pqueue = NULL;
static void
cached_resolve_add_answer(cached_resolve_t *resolve,
int query_type,
int dns_result,
const tor_addr_t *answer_addr,
const char *answer_hostname,
uint32_t ttl)
{
if (query_type == DNS_PTR) {
if (resolve->res_status_hostname != RES_STATUS_INFLIGHT)
return;
if (dns_result == DNS_ERR_NONE && answer_hostname) {
resolve->result_ptr.hostname = tor_strdup(answer_hostname);
resolve->res_status_hostname = RES_STATUS_DONE_OK;
} else {
resolve->result_ptr.err_hostname = dns_result;
resolve->res_status_hostname = RES_STATUS_DONE_ERR;
}
resolve->ttl_hostname = ttl;
} else if (query_type == DNS_IPv4_A) {
if (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT)
return;
if (dns_result == DNS_ERR_NONE && answer_addr) {
tor_assert(tor_addr_family(answer_addr) == AF_INET);
resolve->result_ipv4.addr_ipv4 = tor_addr_to_ipv4h(answer_addr);
resolve->res_status_ipv4 = RES_STATUS_DONE_OK;
} else {
resolve->result_ipv4.err_ipv4 = dns_result;
resolve->res_status_ipv4 = RES_STATUS_DONE_ERR;
}
} else if (query_type == DNS_IPv6_AAAA) {
if (resolve->res_status_ipv6 != RES_STATUS_INFLIGHT)
return;
if (dns_result == DNS_ERR_NONE && answer_addr) {
tor_assert(tor_addr_family(answer_addr) == AF_INET6);
memcpy(&resolve->result_ipv6.addr_ipv6,
tor_addr_to_in6(answer_addr),
sizeof(struct in6_addr));
resolve->res_status_ipv6 = RES_STATUS_DONE_OK;
} else {
resolve->result_ipv6.err_ipv6 = dns_result;
resolve->res_status_ipv6 = RES_STATUS_DONE_ERR;
}
}
}
/*DOCDOC*/
static int
cached_resolve_have_all_answers(const cached_resolve_t *resolve)
{
return (resolve->res_status_ipv4 != RES_STATUS_INFLIGHT &&
resolve->res_status_ipv6 != RES_STATUS_INFLIGHT &&
resolve->res_status_hostname != RES_STATUS_INFLIGHT);
}
/** Set an expiry time for a cached_resolve_t, and add it to the expiry /** Set an expiry time for a cached_resolve_t, and add it to the expiry
* priority queue */ * priority queue */
static void static void
@ -436,8 +522,7 @@ purge_expired_resolves(time_t now)
"Expiring a dns resolve %s that's still pending. Forgot to " "Expiring a dns resolve %s that's still pending. Forgot to "
"cull it? DNS resolve didn't tell us about the timeout?", "cull it? DNS resolve didn't tell us about the timeout?",
escaped_safe_str(resolve->address)); escaped_safe_str(resolve->address));
} else if (resolve->state == CACHE_STATE_CACHED_VALID || } else if (resolve->state == CACHE_STATE_CACHED) {
resolve->state == CACHE_STATE_CACHED_FAILED) {
log_debug(LD_EXIT, log_debug(LD_EXIT,
"Forgetting old cached resolve (address %s, expires %lu)", "Forgetting old cached resolve (address %s, expires %lu)",
escaped_safe_str(resolve->address), escaped_safe_str(resolve->address),
@ -466,8 +551,7 @@ purge_expired_resolves(time_t now)
} }
} }
if (resolve->state == CACHE_STATE_CACHED_VALID || if (resolve->state == CACHE_STATE_CACHED ||
resolve->state == CACHE_STATE_CACHED_FAILED ||
resolve->state == CACHE_STATE_PENDING) { resolve->state == CACHE_STATE_PENDING) {
removed = HT_REMOVE(cache_map, &cache_root, resolve); removed = HT_REMOVE(cache_map, &cache_root, resolve);
if (removed != resolve) { if (removed != resolve) {
@ -482,8 +566,8 @@ purge_expired_resolves(time_t now)
cached_resolve_t *tmp = HT_FIND(cache_map, &cache_root, resolve); cached_resolve_t *tmp = HT_FIND(cache_map, &cache_root, resolve);
tor_assert(tmp != resolve); tor_assert(tmp != resolve);
} }
if (resolve->is_reverse) if (resolve->res_status_hostname == RES_STATUS_DONE_OK)
tor_free(resolve->result.hostname); tor_free(resolve->result_ptr.hostname);
resolve->magic = 0xF0BBF0BB; resolve->magic = 0xF0BBF0BB;
tor_free(resolve); tor_free(resolve);
} }
@ -702,10 +786,10 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
cached_resolve_t *resolve; cached_resolve_t *resolve;
cached_resolve_t search; cached_resolve_t search;
pending_connection_t *pending_connection; pending_connection_t *pending_connection;
int is_reverse = 0;
const routerinfo_t *me; const routerinfo_t *me;
tor_addr_t addr; tor_addr_t addr;
time_t now = time(NULL); time_t now = time(NULL);
uint8_t is_reverse = 0;
int r; int r;
assert_connection_ok(TO_CONN(exitconn), 0); assert_connection_ok(TO_CONN(exitconn), 0);
tor_assert(!SOCKET_OK(exitconn->base_.s)); tor_assert(!SOCKET_OK(exitconn->base_.s));
@ -773,6 +857,7 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
//log_notice(LD_EXIT, "Looks like an address %s", //log_notice(LD_EXIT, "Looks like an address %s",
//exitconn->base_.address); //exitconn->base_.address);
} }
exitconn->is_reverse_dns_lookup = is_reverse;
/* now check the hash table to see if 'address' is already there. */ /* now check the hash table to see if 'address' is already there. */
strlcpy(search.address, exitconn->base_.address, sizeof(search.address)); strlcpy(search.address, exitconn->base_.address, sizeof(search.address));
@ -791,23 +876,13 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
"resolve of %s", exitconn->base_.s, "resolve of %s", exitconn->base_.s,
escaped_safe_str(exitconn->base_.address)); escaped_safe_str(exitconn->base_.address));
return 0; return 0;
case CACHE_STATE_CACHED_VALID: case CACHE_STATE_CACHED:
log_debug(LD_EXIT,"Connection (fd %d) found cached answer for %s", log_debug(LD_EXIT,"Connection (fd %d) found cachedresult for %s",
exitconn->base_.s, exitconn->base_.s,
escaped_safe_str(resolve->address)); escaped_safe_str(resolve->address));
exitconn->address_ttl = resolve->ttl;
if (resolve->is_reverse) { return set_exitconn_info_from_resolve(exitconn, resolve, hostname_out);
tor_assert(is_resolve);
*hostname_out = tor_strdup(resolve->result.hostname);
} else {
tor_addr_from_ipv4h(&exitconn->base_.addr, resolve->result.a.addr);
}
return 1;
case CACHE_STATE_CACHED_FAILED:
log_debug(LD_EXIT,"Connection (fd %d) found cached error for %s",
exitconn->base_.s,
escaped_safe_str(exitconn->base_.address));
return -1;
case CACHE_STATE_DONE: case CACHE_STATE_DONE:
log_err(LD_BUG, "Found a 'DONE' dns resolve still in the cache."); log_err(LD_BUG, "Found a 'DONE' dns resolve still in the cache.");
tor_fragile_assert(); tor_fragile_assert();
@ -820,7 +895,6 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
resolve->magic = CACHED_RESOLVE_MAGIC; resolve->magic = CACHED_RESOLVE_MAGIC;
resolve->state = CACHE_STATE_PENDING; resolve->state = CACHE_STATE_PENDING;
resolve->minheap_idx = -1; resolve->minheap_idx = -1;
resolve->is_reverse = is_reverse;
strlcpy(resolve->address, exitconn->base_.address, sizeof(resolve->address)); strlcpy(resolve->address, exitconn->base_.address, sizeof(resolve->address));
/* add this connection to the pending list */ /* add this connection to the pending list */
@ -837,7 +911,93 @@ dns_resolve_impl(edge_connection_t *exitconn, int is_resolve,
escaped_safe_str(exitconn->base_.address)); escaped_safe_str(exitconn->base_.address));
assert_cache_ok(); assert_cache_ok();
return launch_resolve(exitconn); return launch_resolve(resolve);
}
/* DOCDOC must set address_ttl, addr, etc.*/
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;
tor_assert(exitconn);
tor_assert(resolve);
if (exitconn->is_reverse_dns_lookup) {
exitconn->address_ttl = resolve->ttl_hostname;
if (resolve->res_status_hostname == RES_STATUS_DONE_OK) {
*hostname_out = tor_strdup(resolve->result_ptr.hostname);
return 1;
} else {
return -1;
}
}
/* If we're here then the connection wants one or either of ipv4, ipv6, and
* we can give it one or both. */
ipv4_ok = (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) &&
! (exitconn->begincell_flags & BEGIN_FLAG_IPV4_NOT_OK);
ipv6_ok = (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) &&
(exitconn->begincell_flags & BEGIN_FLAG_IPV6_OK) &&
get_options()->IPv6Exit;
/* Now decide which one to actually give. */
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;
tor_addr_t a4, a6;
tor_addr_from_ipv4h(&a4, resolve->result_ipv4.addr_ipv4);
tor_addr_from_in6(&a6, &resolve->result_ipv6.addr_ipv6);
ipv4_allowed = !router_compare_to_my_exit_policy(&a4, port);
ipv6_allowed = !router_compare_to_my_exit_policy(&a6, port);
if (ipv4_allowed && !ipv6_allowed) {
answer_with_ipv4 = 1;
} else if (ipv6_allowed && !ipv4_allowed) {
answer_with_ipv4 = 0;
} else {
/* Our exit policy would permit both. Answer with whichever the user
* prefers */
answer_with_ipv4 = !(exitconn->begincell_flags &
BEGIN_FLAG_IPV6_PREFERRED);
}
} else {
/* Otherwise if one is okay, send it back. */
if (ipv4_ok) {
answer_with_ipv4 = 1;
} else if (ipv6_ok) {
answer_with_ipv4 = 0;
} else {
/* Neither one was okay. Choose based on user preference. */
answer_with_ipv4 = !(exitconn->begincell_flags &
BEGIN_FLAG_IPV6_PREFERRED);
}
}
/* Finally, we write the answer back. */
r = 1;
if (answer_with_ipv4) {
if (resolve->res_status_ipv4 == RES_STATUS_DONE_OK) {
tor_addr_from_ipv4h(&exitconn->base_.addr,
resolve->result_ipv4.addr_ipv4);
} else {
r = evdns_err_is_transient(resolve->result_ipv4.err_ipv4) ? -2 : -1;
}
exitconn->address_ttl = resolve->ttl_ipv4;
} else {
if (resolve->res_status_ipv6 == RES_STATUS_DONE_OK) {
tor_addr_from_in6(&exitconn->base_.addr,
&resolve->result_ipv6.addr_ipv6);
} else {
r = evdns_err_is_transient(resolve->result_ipv6.err_ipv6) ? -2 : -1;
}
exitconn->address_ttl = resolve->ttl_ipv6;
}
return r;
} }
/** Log an error and abort if conn is waiting for a DNS resolve. /** Log an error and abort if conn is waiting for a DNS resolve.
@ -1011,47 +1171,6 @@ dns_cancel_pending_resolve(const char *address)
resolve->state = CACHE_STATE_DONE; resolve->state = CACHE_STATE_DONE;
} }
/** Helper: adds an entry to the DNS cache mapping <b>address</b> to the ipv4
* address <b>addr</b> (if is_reverse is 0) or the hostname <b>hostname</b> (if
* is_reverse is 1). <b>ttl</b> is a cache ttl; <b>outcome</b> is one of
* DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}.
**/
static void
add_answer_to_cache(const char *address, uint8_t is_reverse, uint32_t addr,
const char *hostname, char outcome, uint32_t ttl)
{
cached_resolve_t *resolve;
if (outcome == DNS_RESOLVE_FAILED_TRANSIENT)
return;
//log_notice(LD_EXIT, "Adding to cache: %s -> %s (%lx, %s), %d",
// address, is_reverse?"(reverse)":"", (unsigned long)addr,
// hostname?hostname:"NULL",(int)outcome);
resolve = tor_malloc_zero(sizeof(cached_resolve_t));
resolve->magic = CACHED_RESOLVE_MAGIC;
resolve->state = (outcome == DNS_RESOLVE_SUCCEEDED) ?
CACHE_STATE_CACHED_VALID : CACHE_STATE_CACHED_FAILED;
strlcpy(resolve->address, address, sizeof(resolve->address));
resolve->is_reverse = is_reverse;
if (is_reverse) {
if (outcome == DNS_RESOLVE_SUCCEEDED) {
tor_assert(hostname);
resolve->result.hostname = tor_strdup(hostname);
} else {
tor_assert(! hostname);
resolve->result.hostname = NULL;
}
} else {
tor_assert(!hostname);
resolve->result.a.addr = addr;
}
resolve->ttl = ttl;
assert_resolve_ok(resolve);
HT_INSERT(cache_map, &cache_root, resolve);
set_expiry(resolve, time(NULL) + dns_get_expiry_ttl(ttl));
}
/** Return true iff <b>address</b> is one of the addresses we use to verify /** 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. */ * that well-known sites aren't being hijacked by our DNS servers. */
static INLINE int static INLINE int
@ -1070,14 +1189,13 @@ is_test_address(const char *address)
* DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}. * DNS_RESOLVE_{FAILED_TRANSIENT|FAILED_PERMANENT|SUCCEEDED}.
*/ */
static void static void
dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr, dns_found_answer(const char *address, uint8_t query_type,
const char *hostname, char outcome, uint32_t ttl) int dns_answer,
const tor_addr_t *addr,
const char *hostname, uint32_t ttl)
{ {
pending_connection_t *pend;
cached_resolve_t search; cached_resolve_t search;
cached_resolve_t *resolve, *removed; cached_resolve_t *resolve;
edge_connection_t *pendconn;
circuit_t *circ;
assert_cache_ok(); assert_cache_ok();
@ -1087,9 +1205,8 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
if (!resolve) { if (!resolve) {
int is_test_addr = is_test_address(address); int is_test_addr = is_test_address(address);
if (!is_test_addr) if (!is_test_addr)
log_info(LD_EXIT,"Resolved unasked address %s; caching anyway.", log_info(LD_EXIT,"Resolved unasked address %s; ignoring.",
escaped_safe_str(address)); escaped_safe_str(address));
add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl);
return; return;
} }
assert_resolve_ok(resolve); assert_resolve_ok(resolve);
@ -1105,17 +1222,32 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
tor_assert(resolve->pending_connections == NULL); tor_assert(resolve->pending_connections == NULL);
return; return;
} }
/* Removed this assertion: in fact, we'll sometimes get a double answer
* to the same question. This can happen when we ask one worker to resolve cached_resolve_add_answer(resolve, query_type, dns_answer,
* X.Y.Z., then we cancel the request, and then we ask another worker to addr, hostname, ttl);
* resolve X.Y.Z. */
/* tor_assert(resolve->state == CACHE_STATE_PENDING); */ if (cached_resolve_have_all_answers(resolve)) {
inform_pending_connections(resolve);
make_pending_resolve_cached(resolve);
}
}
/*DOCDOC*/
static void
inform_pending_connections(cached_resolve_t *resolve)
{
pending_connection_t *pend;
edge_connection_t *pendconn;
int r;
while (resolve->pending_connections) { while (resolve->pending_connections) {
char *hostname = NULL;
pend = resolve->pending_connections; pend = resolve->pending_connections;
pendconn = pend->conn; /* don't pass complex things to the pendconn = pend->conn; /* don't pass complex things to the
connection_mark_for_close macro */ connection_mark_for_close macro */
assert_connection_ok(TO_CONN(pendconn),time(NULL)); assert_connection_ok(TO_CONN(pendconn),time(NULL));
if (pendconn->base_.marked_for_close) { if (pendconn->base_.marked_for_close) {
/* prevent double-remove. */ /* prevent double-remove. */
pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED;
@ -1123,10 +1255,12 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
tor_free(pend); tor_free(pend);
continue; continue;
} }
tor_addr_from_ipv4h(&pendconn->base_.addr, addr);
pendconn->address_ttl = ttl;
if (outcome != DNS_RESOLVE_SUCCEEDED) { r = set_exitconn_info_from_resolve(pendconn,
resolve,
&hostname);
if (r < 0) {
/* prevent double-remove. */ /* prevent double-remove. */
pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED;
if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) {
@ -1134,15 +1268,15 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
/* This detach must happen after we send the end cell. */ /* This detach must happen after we send the end cell. */
circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn);
} else { } else {
send_resolved_cell(pendconn, outcome == DNS_RESOLVE_FAILED_PERMANENT ? send_resolved_cell(pendconn, r == -1 ?
RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT); RESOLVED_TYPE_ERROR : RESOLVED_TYPE_ERROR_TRANSIENT);
/* This detach must happen after we send the resolved cell. */ /* This detach must happen after we send the resolved cell. */
circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn); circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn);
} }
connection_free(TO_CONN(pendconn)); connection_free(TO_CONN(pendconn));
} else { } else {
circuit_t *circ;
if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) { if (pendconn->base_.purpose == EXIT_PURPOSE_CONNECT) {
tor_assert(!is_reverse);
/* prevent double-remove. */ /* prevent double-remove. */
pend->conn->base_.state = EXIT_CONN_STATE_CONNECTING; pend->conn->base_.state = EXIT_CONN_STATE_CONNECTING;
@ -1161,7 +1295,7 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
/* prevent double-remove. This isn't really an accurate state, /* prevent double-remove. This isn't really an accurate state,
* but it does the right thing. */ * but it does the right thing. */
pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED; pendconn->base_.state = EXIT_CONN_STATE_RESOLVEFAILED;
if (is_reverse) if (pendconn->is_reverse_dns_lookup)
send_resolved_hostname_cell(pendconn, hostname); send_resolved_hostname_cell(pendconn, hostname);
else else
send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO); send_resolved_cell(pendconn, RESOLVED_TYPE_AUTO);
@ -1174,9 +1308,16 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
resolve->pending_connections = pend->next; resolve->pending_connections = pend->next;
tor_free(pend); tor_free(pend);
} }
}
/*DOCDOC*/
static void
make_pending_resolve_cached(cached_resolve_t *resolve)
{
cached_resolve_t *removed;
resolve->state = CACHE_STATE_DONE; resolve->state = CACHE_STATE_DONE;
removed = HT_REMOVE(cache_map, &cache_root, &search); removed = HT_REMOVE(cache_map, &cache_root, resolve);
if (removed != resolve) { if (removed != resolve) {
log_err(LD_BUG, "The pending resolve we found wasn't removable from" log_err(LD_BUG, "The pending resolve we found wasn't removable from"
" the cache. Tried to purge %s (%p); instead got %s (%p).", " the cache. Tried to purge %s (%p); instead got %s (%p).",
@ -1185,8 +1326,42 @@ dns_found_answer(const char *address, uint8_t is_reverse, uint32_t addr,
} }
assert_resolve_ok(resolve); assert_resolve_ok(resolve);
assert_cache_ok(); assert_cache_ok();
/* The resolve will eventually just hit the time-out in the expiry queue and
* expire. See fd0bafb0dedc7e2 for a brief explanation of how this got that
* way. XXXXX we could do better!*/
{
cached_resolve_t *new_resolve = tor_memdup(resolve,
sizeof(cached_resolve_t));
uint32_t ttl = UINT32_MAX;
new_resolve->expiry = 0; /* So that set_expiry won't croak. */
if (resolve->res_status_hostname == RES_STATUS_DONE_OK)
new_resolve->result_ptr.hostname =
tor_strdup(resolve->result_ptr.hostname);
new_resolve->state = CACHE_STATE_CACHED;
assert_resolve_ok(new_resolve);
HT_INSERT(cache_map, &cache_root, new_resolve);
if ((resolve->res_status_ipv4 == RES_STATUS_DONE_OK ||
resolve->res_status_ipv4 == RES_STATUS_DONE_ERR) &&
resolve->ttl_ipv4 < ttl)
ttl = resolve->ttl_ipv4;
if ((resolve->res_status_ipv6 == RES_STATUS_DONE_OK ||
resolve->res_status_ipv6 == RES_STATUS_DONE_ERR) &&
resolve->ttl_ipv6 < ttl)
ttl = resolve->ttl_ipv6;
if ((resolve->res_status_hostname == RES_STATUS_DONE_OK ||
resolve->res_status_hostname == RES_STATUS_DONE_ERR) &&
resolve->ttl_hostname < ttl)
ttl = resolve->ttl_hostname;
set_expiry(new_resolve, time(NULL) + dns_get_expiry_ttl(ttl));
}
add_answer_to_cache(address, is_reverse, addr, hostname, outcome, ttl);
assert_cache_ok(); assert_cache_ok();
} }
@ -1342,10 +1517,6 @@ configure_nameservers(int force)
return -1; return -1;
} }
static uint64_t n_ipv6_requests_made = 0;
static uint64_t n_ipv6_timeouts = 0;
static int dns_is_broken_for_ipv6 = 0;
/** For eventdns: Called when we get an answer for a request we launched. /** For eventdns: Called when we get an answer for a request we launched.
* See eventdns.h for arguments; 'arg' holds the address we tried to resolve. * See eventdns.h for arguments; 'arg' holds the address we tried to resolve.
*/ */
@ -1353,8 +1524,9 @@ static void
evdns_callback(int result, char type, int count, int ttl, void *addresses, evdns_callback(int result, char type, int count, int ttl, void *addresses,
void *arg) void *arg)
{ {
char *string_address = arg; char *arg_ = arg;
uint8_t is_reverse = 0; uint8_t orig_query_type = arg_[0];
char *string_address = arg_ + 1;
int status = DNS_RESOLVE_FAILED_PERMANENT; int status = DNS_RESOLVE_FAILED_PERMANENT;
tor_addr_t addr; tor_addr_t addr;
const char *hostname = NULL; const char *hostname = NULL;
@ -1382,7 +1554,6 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
if (result == DNS_ERR_NONE) { if (result == DNS_ERR_NONE) {
if (type == DNS_IPv4_A && count) { if (type == DNS_IPv4_A && count) {
char answer_buf[INET_NTOA_BUF_LEN+1]; char answer_buf[INET_NTOA_BUF_LEN+1];
struct in_addr in;
char *escaped_address; char *escaped_address;
uint32_t *addrs = addresses; uint32_t *addrs = addresses;
tor_addr_from_ipv4n(&addr, addrs[0]); tor_addr_from_ipv4n(&addr, addrs[0]);
@ -1397,7 +1568,7 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
safe_str(escaped_address), safe_str(escaped_address),
escaped_safe_str(answer_buf)); escaped_safe_str(answer_buf));
was_wildcarded = 1; was_wildcarded = 1;
addr = 0; tor_addr_make_null(&addr, AF_INET); /* ???? */
status = DNS_RESOLVE_FAILED_PERMANENT; status = DNS_RESOLVE_FAILED_PERMANENT;
} else { } else {
log_debug(LD_EXIT, "eventdns said that %s resolves to %s", log_debug(LD_EXIT, "eventdns said that %s resolves to %s",
@ -1430,7 +1601,6 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
tor_free(escaped_address); tor_free(escaped_address);
} else if (type == DNS_PTR && count) { } else if (type == DNS_PTR && count) {
char *escaped_address; char *escaped_address;
is_reverse = 1;
hostname = ((char**)addresses)[0]; hostname = ((char**)addresses)[0];
status = DNS_RESOLVE_SUCCEEDED; status = DNS_RESOLVE_SUCCEEDED;
escaped_address = esc_for_log(string_address); escaped_address = esc_for_log(string_address);
@ -1456,23 +1626,76 @@ evdns_callback(int result, char type, int count, int ttl, void *addresses,
add_wildcarded_test_address(string_address); add_wildcarded_test_address(string_address);
} }
} }
if (orig_query_type && type && orig_query_type != type) {
log_warn(LD_BUG, "Weird; orig_query_type == %d but type == %d",
(int)orig_query_type, (int)type);
}
if (result != DNS_ERR_SHUTDOWN) if (result != DNS_ERR_SHUTDOWN)
dns_found_answer(string_address, is_reverse, addr, hostname, status, ttl); dns_found_answer(string_address, orig_query_type,
tor_free(string_address); result, &addr, hostname, ttl);
tor_free(arg_);
}
/**DOCDOC*/
static int
launch_one_resolve(const char *address, uint8_t query_type,
const tor_addr_t *ptr_address)
{
const int options = get_options()->ServerDNSSearchDomains ? 0
: DNS_QUERY_NO_SEARCH;
const size_t addr_len = strlen(address);
struct evdns_request *req = 0;
char *addr = tor_malloc(addr_len + 2);
addr[0] = (char) query_type;
memcpy(addr+1, address, addr_len + 1);
switch (query_type) {
case DNS_IPv4_A:
req = evdns_base_resolve_ipv4(the_evdns_base,
address, options, evdns_callback, addr);
break;
case DNS_IPv6_AAAA:
req = evdns_base_resolve_ipv6(the_evdns_base,
address, options, evdns_callback, addr);
++n_ipv6_requests_made;
break;
case DNS_PTR:
if (tor_addr_family(ptr_address) == AF_INET)
req = evdns_base_resolve_reverse(the_evdns_base,
tor_addr_to_in(ptr_address),
DNS_QUERY_NO_SEARCH,
evdns_callback, addr);
else if (tor_addr_family(ptr_address) == AF_INET6)
req = evdns_base_resolve_reverse_ipv6(the_evdns_base,
tor_addr_to_in6(ptr_address),
DNS_QUERY_NO_SEARCH,
evdns_callback, addr);
else
log_warn(LD_BUG, "Called with PTR query and unexpected address family");
break;
default:
log_warn(LD_BUG, "Called with unexpectd query type %d", (int)query_type);
break;
}
if (req) {
return 0;
} else {
tor_free(addr);
return -1;
}
} }
/** For eventdns: start resolving as necessary to find the target for /** For eventdns: start resolving as necessary to find the target for
* <b>exitconn</b>. Returns -1 on error, -2 on transient error, * <b>exitconn</b>. Returns -1 on error, -2 on transient error,
* 0 on "resolve launched." */ * 0 on "resolve launched." */
static int static int
launch_resolve(edge_connection_t *exitconn) launch_resolve(cached_resolve_t *resolve)
{ {
char *addr;
struct evdns_request *req = NULL;
tor_addr_t a; tor_addr_t a;
int r; int r;
int options = get_options()->ServerDNSSearchDomains ? 0
: DNS_QUERY_NO_SEARCH;
if (get_options()->DisableNetwork) if (get_options()->DisableNetwork)
return -1; return -1;
@ -1486,40 +1709,45 @@ launch_resolve(edge_connection_t *exitconn)
} }
} }
addr = tor_strdup(exitconn->base_.address);
r = tor_addr_parse_PTR_name( r = tor_addr_parse_PTR_name(
&a, exitconn->base_.address, AF_UNSPEC, 0); &a, resolve->address, AF_UNSPEC, 0);
tor_assert(the_evdns_base); tor_assert(the_evdns_base);
if (r == 0) { if (r == 0) {
log_info(LD_EXIT, "Launching eventdns request for %s", log_info(LD_EXIT, "Launching eventdns request for %s",
escaped_safe_str(exitconn->base_.address)); escaped_safe_str(resolve->address));
req = evdns_base_resolve_ipv4(the_evdns_base, resolve->res_status_ipv4 = RES_STATUS_INFLIGHT;
exitconn->base_.address, options, if (get_options()->IPv6Exit)
evdns_callback, addr); resolve->res_status_ipv6 = RES_STATUS_INFLIGHT;
if (launch_one_resolve(resolve->address, DNS_IPv4_A, NULL) < 0) {
resolve->res_status_ipv4 = 0;
r = -1;
}
if (r==0 && get_options()->IPv6Exit) {
/* We ask for an IPv6 address for *everything*. */
if (launch_one_resolve(resolve->address, DNS_IPv6_AAAA, NULL) < 0) {
resolve->res_status_ipv6 = 0;
r = -1;
}
}
} else if (r == 1) { } else if (r == 1) {
r = 0;
log_info(LD_EXIT, "Launching eventdns reverse request for %s", log_info(LD_EXIT, "Launching eventdns reverse request for %s",
escaped_safe_str(exitconn->base_.address)); escaped_safe_str(resolve->address));
if (tor_addr_family(&a) == AF_INET) resolve->res_status_hostname = RES_STATUS_INFLIGHT;
req = evdns_base_resolve_reverse(the_evdns_base, if (launch_one_resolve(resolve->address, DNS_PTR, &a) < 0) {
tor_addr_to_in(&a), DNS_QUERY_NO_SEARCH, resolve->res_status_hostname = 0;
evdns_callback, addr); r = -1;
else }
req = evdns_base_resolve_reverse_ipv6(the_evdns_base,
tor_addr_to_in6(&a), DNS_QUERY_NO_SEARCH,
evdns_callback, addr);
} else if (r == -1) { } else if (r == -1) {
log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here."); log_warn(LD_BUG, "Somehow a malformed in-addr.arpa address reached here.");
} }
r = 0; if (r < 0) {
if (!req) {
log_fn(LOG_PROTOCOL_WARN, LD_EXIT, "eventdns rejected address %s.", log_fn(LOG_PROTOCOL_WARN, LD_EXIT, "eventdns rejected address %s.",
escaped_safe_str(addr)); escaped_safe_str(resolve->address));
r = -1;
tor_free(addr); /* There is no evdns request in progress; stop
* addr from getting leaked. */
} }
return r; return r;
} }
@ -1699,7 +1927,6 @@ static void
launch_test_addresses(int fd, short event, void *args) launch_test_addresses(int fd, short event, void *args)
{ {
const or_options_t *options = get_options(); const or_options_t *options = get_options();
struct evdns_request *req;
(void)fd; (void)fd;
(void)event; (void)event;
(void)args; (void)args;
@ -1717,23 +1944,14 @@ launch_test_addresses(int fd, short event, void *args)
tor_assert(the_evdns_base); tor_assert(the_evdns_base);
SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses, SMARTLIST_FOREACH_BEGIN(options->ServerDNSTestAddresses,
const char *, address) { const char *, address) {
char *a = tor_strdup(address); if (launch_one_resolve(address, DNS_IPv4_A, NULL) < 0) {
req = evdns_base_resolve_ipv4(the_evdns_base,
address, DNS_QUERY_NO_SEARCH, evdns_callback, a);
if (!req) {
log_info(LD_EXIT, "eventdns rejected test address %s", log_info(LD_EXIT, "eventdns rejected test address %s",
escaped_safe_str(address)); escaped_safe_str(address));
tor_free(a);
} }
a = tor_strdup(address);
req = evdns_base_resolve_ipv6(the_evdns_base, if (launch_one_resolve(address, DNS_IPv6_AAAA, NULL) < 0) {
address, DNS_QUERY_NO_SEARCH, evdns_callback, a);
++n_ipv6_requests_made;
if (!req) {
log_info(LD_EXIT, "eventdns rejected test address %s", log_info(LD_EXIT, "eventdns rejected test address %s",
escaped_safe_str(address)); escaped_safe_str(address));
tor_free(a);
} }
} SMARTLIST_FOREACH_END(address); } SMARTLIST_FOREACH_END(address);
} }
@ -1857,11 +2075,14 @@ assert_resolve_ok(cached_resolve_t *resolve)
} }
if (resolve->state == CACHE_STATE_PENDING || if (resolve->state == CACHE_STATE_PENDING ||
resolve->state == CACHE_STATE_DONE) { resolve->state == CACHE_STATE_DONE) {
#if 0
tor_assert(!resolve->ttl); tor_assert(!resolve->ttl);
if (resolve->is_reverse) if (resolve->is_reverse)
tor_assert(!resolve->result.hostname); tor_assert(!resolve->hostname);
else else
tor_assert(!resolve->result.a.addr); tor_assert(!resolve->result_ipv4.addr_ipv4);
#endif
/*XXXXX ADD MORE */
} }
} }

View file

@ -1440,6 +1440,8 @@ typedef struct edge_connection_t {
/** True iff this connection is for a DNS request only. */ /** True iff this connection is for a DNS request only. */
unsigned int is_dns_request:1; unsigned int is_dns_request:1;
/* DOCDOC exit only */
unsigned int is_reverse_dns_lookup:1;
unsigned int edge_has_sent_end:1; /**< For debugging; only used on edge unsigned int edge_has_sent_end:1; /**< For debugging; only used on edge
* connections. Set once we've set the stream end, * connections. Set once we've set the stream end,