📄 dns.c
字号:
/* Copyright (c) 2003-2004, Roger Dingledine.
* Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
* Copyright (c) 2007-2008, The Tor Project, Inc. */
/* See LICENSE for licensing information */
/* $Id$ */
const char dns_c_id[] =
"$Id$";
/**
* \file dns.c
* \brief Implements a local cache for DNS results for Tor servers.
* 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.)
**/
#include "or.h"
#include "ht.h"
#include "eventdns.h"
/** 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
/** Possible outcomes from hostname lookup: permanent failure,
* transient (retryable) failure, and success. */
#define DNS_RESOLVE_FAILED_TRANSIENT 1
#define DNS_RESOLVE_FAILED_PERMANENT 2
#define DNS_RESOLVE_SUCCEEDED 3
/** Have we currently configured nameservers with eventdns? */
static int nameservers_configured = 0;
/** What was the resolv_conf fname we last used when configuring the
* nameservers? Used to check whether we need to reconfigure. */
static char *resolv_conf_fname = NULL;
/** What was the mtime on the resolv.conf file we last used when configuring
* 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_VALID or a CACHED_FAILED
* 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_VALID 2
/** We are caching a failure for this address. This should have no pending
* connections, and should appear in the hash table */
#define CACHE_STATE_CACHED_FAILED 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;
char address[MAX_ADDRESSLEN]; /**< The hostname to be resolved. */
union {
uint32_t addr; /**< IPv4 addr for <b>address</b>. */
char *hostname; /**< Hostname for <b>address</b> (if a reverse lookup) */
} result;
uint8_t state; /**< Is this cached entry pending/done/valid/failed? */
uint8_t is_reverse; /**< Is this a reverse (addr-to-hostname) lookup? */
time_t expire; /**< Remove items from cache after this time. */
uint32_t ttl; /**< 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;
} cached_resolve_t;
static void purge_expired_resolves(time_t now);
static void dns_found_answer(const char *address, uint8_t is_reverse,
uint32_t addr, const char *hostname, char outcome,
uint32_t ttl);
static void send_resolved_cell(edge_connection_t *conn, uint8_t answer_type);
static int launch_resolve(edge_connection_t *exitconn);
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);
#ifdef DEBUG_DNS_CACHE
static void _assert_cache_ok(void);
#define assert_cache_ok() _assert_cache_ok()
#else
#define assert_cache_ok() STMT_NIL
#endif
static void assert_resolve_ok(cached_resolve_t *resolve);
/** Hash table of cached_resolve objects. */
static HT_HEAD(cache_map, cached_resolve_t) cache_root;
/** Function to compare hashed resolves on their addresses; used to
* implement hash tables. */
static INLINE int
cached_resolves_eq(cached_resolve_t *a, cached_resolve_t *b)
{
/* make this smarter one day? */
assert_resolve_ok(a); // Not b; b may be just a search.
return !strncmp(a->address, b->address, MAX_ADDRESSLEN);
}
/** Hash function for cached_resolve objects */
static INLINE unsigned int
cached_resolve_hash(cached_resolve_t *a)
{
return ht_string_hash(a->address);
}
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)
/** Initialize the DNS cache. */
static void
init_cache_map(void)
{
HT_INIT(cache_map, &cache_root);
}
/** Helper: called by eventdns when eventdns wants to log something. */
static void
evdns_log_cb(int warn, const char *msg)
{
const char *cp;
static int all_down = 0;
int severity = warn ? LOG_WARN : LOG_INFO;
if (!strcmpstart(msg, "Resolve requested for") &&
get_options()->SafeLogging) {
log(LOG_INFO, LD_EXIT, "eventdns: Resolve requested.");
return;
} else if (!strcmpstart(msg, "Search: ")) {
return;
}
if (!strcmpstart(msg, "Nameserver ") && (cp=strstr(msg, " has failed: "))) {
char *ns = tor_strndup(msg+11, cp-(msg+11));
const char *err = strchr(cp, ':')+2;
tor_assert(err);
/* Don't warn about a single failed nameserver; we'll warn with 'all
* nameservers have failed' if we're completely out of nameservers;
* otherwise, the situation is tolerable. */
severity = LOG_INFO;
control_event_server_status(LOG_NOTICE,
"NAMESERVER_STATUS NS=%s STATUS=DOWN ERR=%s",
ns, escaped(err));
tor_free(ns);
} else if (!strcmpstart(msg, "Nameserver ") &&
(cp=strstr(msg, " is back up"))) {
char *ns = tor_strndup(msg+11, cp-(msg+11));
severity = (all_down && warn) ? LOG_NOTICE : LOG_INFO;
all_down = 0;
control_event_server_status(LOG_NOTICE,
"NAMESERVER_STATUS NS=%s STATUS=UP", ns);
tor_free(ns);
} else if (!strcmp(msg, "All nameservers have failed")) {
control_event_server_status(LOG_WARN, "NAMESERVER_ALL_DOWN");
all_down = 1;
}
log(severity, LD_EXIT, "eventdns: %s", msg);
}
/** Helper: generate a good random transaction ID. */
static uint16_t
dns_get_transaction_id(void)
{
uint16_t result;
crypto_rand((void*)&result, sizeof(result));
return result;
}
/** Initialize the DNS subsystem; called by the OR process. */
int
dns_init(void)
{
init_cache_map();
evdns_set_transaction_id_fn(dns_get_transaction_id);
if (server_mode(get_options()))
return configure_nameservers(1);
return 0;
}
/** Called when DNS-related options change (or may have changed). Returns -1
* on failure, 0 on success. */
int
dns_reset(void)
{
or_options_t *options = get_options();
if (! server_mode(options)) {
evdns_clear_nameservers_and_suspend();
evdns_search_clear();
nameservers_configured = 0;
tor_free(resolv_conf_fname);
resolv_conf_mtime = 0;
} else {
if (configure_nameservers(0) < 0)
return -1;
}
return 0;
}
/** Helper: Given a TTL from a DNS response, determine what TTL to give the
* OP that asked us to resolve it. */
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;
else
return ttl;
}
/** Helper: free storage held by an entry in the DNS cache. */
static void
_free_cached_resolve(cached_resolve_t *r)
{
while (r->pending_connections) {
pending_connection_t *victim = r->pending_connections;
r->pending_connections = victim->next;
tor_free(victim);
}
if (r->is_reverse)
tor_free(r->result.hostname);
r->magic = 0xFF00FF00;
tor_free(r);
}
/** Compare two cached_resolve_t pointers by expiry time, and return
* less-than-zero, zero, or greater-than-zero as appropriate. Used for
* the priority queue implementation. */
static int
_compare_cached_resolves_by_expiry(const void *_a, const void *_b)
{
const cached_resolve_t *a = _a, *b = _b;
if (a->expire < b->expire)
return -1;
else if (a->expire == b->expire)
return 0;
else
return 1;
}
/** Priority queue of cached_resolve_t objects to let us know when they
* will expire. */
static smartlist_t *cached_resolve_pqueue = NULL;
/** Set an expiry time for a cached_resolve_t, and add it to the expiry
* priority queue */
static void
set_expiry(cached_resolve_t *resolve, time_t expires)
{
tor_assert(resolve && resolve->expire == 0);
if (!cached_resolve_pqueue)
cached_resolve_pqueue = smartlist_create();
resolve->expire = expires;
smartlist_pqueue_add(cached_resolve_pqueue,
_compare_cached_resolves_by_expiry,
resolve);
}
/** Free all storage held in the DNS cache and related structures. */
void
dns_free_all(void)
{
cached_resolve_t **ptr, **next, *item;
assert_cache_ok();
if (cached_resolve_pqueue) {
SMARTLIST_FOREACH(cached_resolve_pqueue, cached_resolve_t *, res,
{
if (res->state == CACHE_STATE_DONE)
_free_cached_resolve(res);
});
}
for (ptr = HT_START(cache_map, &cache_root); ptr != NULL; ptr = next) {
item = *ptr;
next = HT_NEXT_RMV(cache_map, &cache_root, ptr);
_free_cached_resolve(item);
}
HT_CLEAR(cache_map, &cache_root);
if (cached_resolve_pqueue)
smartlist_free(cached_resolve_pqueue);
cached_resolve_pqueue = NULL;
tor_free(resolv_conf_fname);
}
/** Remove every cached_resolve whose <b>expire</b> time is before or
* equal to <b>now</b> from the cache. */
static void
purge_expired_resolves(time_t now)
{
cached_resolve_t *resolve, *removed;
pending_connection_t *pend;
edge_connection_t *pendconn;
assert_cache_ok();
if (!cached_resolve_pqueue)
return;
while (smartlist_len(cached_resolve_pqueue)) {
resolve = smartlist_get(cached_resolve_pqueue, 0);
if (resolve->expire > now)
break;
smartlist_pqueue_pop(cached_resolve_pqueue,
_compare_cached_resolves_by_expiry);
if (resolve->state == CACHE_STATE_PENDING) {
log_debug(LD_EXIT,
"Expiring a dns resolve %s that's still pending. Forgot to "
"cull it? DNS resolve didn't tell us about the timeout?",
escaped_safe_str(resolve->address));
} else if (resolve->state == CACHE_STATE_CACHED_VALID ||
resolve->state == CACHE_STATE_CACHED_FAILED) {
log_debug(LD_EXIT,
"Forgetting old cached resolve (address %s, expires %lu)",
escaped_safe_str(resolve->address),
(unsigned long)resolve->expire);
tor_assert(!resolve->pending_connections);
} else {
tor_assert(resolve->state == CACHE_STATE_DONE);
tor_assert(!resolve->pending_connections);
}
if (resolve->pending_connections) {
log_debug(LD_EXIT,
"Closing pending connections on timed-out DNS resolve!");
tor_fragile_assert();
while (resolve->pending_connections) {
pend = resolve->pending_connections;
resolve->pending_connections = pend->next;
/* Connections should only be pending if they have no socket. */
tor_assert(pend->conn->_base.s == -1);
pendconn = pend->conn;
connection_edge_end(pendconn, END_STREAM_REASON_TIMEOUT);
circuit_detach_stream(circuit_get_by_edge_conn(pendconn), pendconn);
connection_free(TO_CONN(pendconn));
tor_free(pend);
}
}
if (resolve->state == CACHE_STATE_CACHED_VALID ||
resolve->state == CACHE_STATE_CACHED_FAILED ||
resolve->state == CACHE_STATE_PENDING) {
removed = HT_REMOVE(cache_map, &cache_root, resolve);
if (removed != resolve) {
log_err(LD_BUG, "The expired resolve we purged didn't match any in"
" the cache. Tried to purge %s (%p); instead got %s (%p).",
resolve->address, (void*)resolve,
removed ? removed->address : "NULL", (void*)remove);
}
tor_assert(removed == resolve);
} else {
/* This should be in state DONE. Make sure it's not in the cache. */
cached_resolve_t *tmp = HT_FIND(cache_map, &cache_root, resolve);
tor_assert(tmp != resolve);
}
if (resolve->is_reverse)
tor_free(resolve->result.hostname);
resolve->magic = 0xF0BBF0BB;
tor_free(resolve);
}
assert_cache_ok();
}
/** Send a response to the RESOLVE request of a connection.
* <b>answer_type</b> must be one of
* RESOLVED_TYPE_(IPV4|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.
*/
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -