diff options
Diffstat (limited to 'src/or/dns.c')
-rw-r--r-- | src/or/dns.c | 102 |
1 files changed, 90 insertions, 12 deletions
diff --git a/src/or/dns.c b/src/or/dns.c index 5a66f8342f..16c55ee6f6 100644 --- a/src/or/dns.c +++ b/src/or/dns.c @@ -20,7 +20,8 @@ int num_workers=0; int num_workers_busy=0; static int dns_assign_to_worker(connection_t *exitconn); -static int dns_found_answer(char *question, uint32_t answer); +static void dns_cancel_pending_resolve(char *question); +static void dns_found_answer(char *question, uint32_t answer); static void dnsworker_main(int fd); static int dns_spawn_worker(void); static void spawn_enough_workers(void); @@ -62,6 +63,9 @@ void dns_init(void) { spawn_enough_workers(); } +static struct cached_resolve *oldest_cached_resolve = NULL; /* linked list, */ +static struct cached_resolve *newest_cached_resolve = NULL; /* oldest to newest */ + /* See if the question 'exitconn->address' has been answered. if so, * if resolve valid, put it into exitconn->addr and exec to * connection_exit_connect. If resolve failed, return -1. @@ -76,10 +80,24 @@ int dns_resolve(connection_t *exitconn) { struct cached_resolve *resolve; struct cached_resolve search; struct pending_connection_t *pending_connection; + uint32_t now = time(NULL); - strncpy(search.question, exitconn->address, MAX_ADDRESSLEN); + /* first take this opportunity to see if there are any expired + * resolves in the tree. this is fast because the linked list + * oldest_cached_resolve is ordered by when they came in. + */ + while(oldest_cached_resolve && (oldest_cached_resolve->expire < now)) { + resolve = oldest_cached_resolve; + log(LOG_DEBUG,"Forgetting old cached resolve (expires %d)", resolve->expire); + oldest_cached_resolve = resolve->next; + if(!oldest_cached_resolve) /* if there are no more, */ + newest_cached_resolve = NULL; /* then make sure the list's tail knows that too */ + SPLAY_REMOVE(cache_tree, &cache_root, resolve); + free(resolve); + } - /* check the tree to see if 'question' is already there. */ + /* now check the tree to see if 'question' is already there. */ + strncpy(search.question, exitconn->address, MAX_ADDRESSLEN); resolve = SPLAY_FIND(cache_tree, &cache_root, &search); if(resolve) { /* already there */ switch(resolve->state) { @@ -100,6 +118,8 @@ int dns_resolve(connection_t *exitconn) { resolve = tor_malloc(sizeof(struct cached_resolve)); memset(resolve, 0, sizeof(struct cached_resolve)); resolve->state = CACHE_STATE_PENDING; + resolve->expire = now + 100; /* XXX for testing. when we're confident, switch it back */ +// resolve->expire = now + 86400; /* now + 1 day */ strncpy(resolve->question, exitconn->address, MAX_ADDRESSLEN); /* add us to the pending list */ @@ -108,6 +128,14 @@ int dns_resolve(connection_t *exitconn) { pending_connection->next = resolve->pending_connections; resolve->pending_connections = pending_connection; + /* add us to the linked list of resolves */ + if (!oldest_cached_resolve) { + oldest_cached_resolve = resolve; + } else { + newest_cached_resolve->next = resolve; + } + newest_cached_resolve = resolve; + SPLAY_INSERT(cache_tree, &cache_root, resolve); return dns_assign_to_worker(exitconn); } @@ -126,6 +154,7 @@ static int dns_assign_to_worker(connection_t *exitconn) { if(!dnsconn) { log(LOG_INFO,"dns_assign_to_worker(): no idle dns workers. Failing."); + dns_cancel_pending_resolve(exitconn->address); return -1; } @@ -139,6 +168,7 @@ static int dns_assign_to_worker(connection_t *exitconn) { connection_write_to_buf(dnsconn->address, len, dnsconn) < 0) { log(LOG_NOTICE,"dns_assign_to_worker(): Write failed. Closing worker and failing resolve."); dnsconn->marked_for_close = 1; + dns_cancel_pending_resolve(exitconn->address); return -1; } @@ -146,7 +176,51 @@ static int dns_assign_to_worker(connection_t *exitconn) { return 0; } -static int dns_found_answer(char *question, uint32_t answer) { +static void dns_cancel_pending_resolve(char *question) { + struct pending_connection_t *pend; + struct cached_resolve search; + struct cached_resolve *resolve, *tmp; + + strncpy(search.question, question, MAX_ADDRESSLEN); + + resolve = SPLAY_FIND(cache_tree, &cache_root, &search); + if(!resolve) { + log_fn(LOG_ERR,"Answer to unasked question '%s'? Dropping.", question); + return; + } + + assert(resolve->state == CACHE_STATE_PENDING); + + /* mark all pending connections to fail */ + while(resolve->pending_connections) { + pend = resolve->pending_connections; + pend->conn->marked_for_close = 1; + resolve->pending_connections = pend->next; + free(pend); + } + + /* remove resolve from the linked list */ + if(resolve == oldest_cached_resolve) { + oldest_cached_resolve = resolve->next; + if(oldest_cached_resolve == NULL) + newest_cached_resolve = NULL; + } else { + /* FFFF make it a doubly linked list if this becomes too slow */ + for(tmp=oldest_cached_resolve; tmp && tmp->next != resolve; tmp=tmp->next) ; + assert(tmp); /* it's got to be in the list, or we screwed up somewhere else */ + tmp->next = resolve->next; /* unlink it */ + + if(newest_cached_resolve == resolve) + newest_cached_resolve = tmp; + } + + /* remove resolve from the tree */ + SPLAY_REMOVE(cache_tree, &cache_root, resolve); + + free(resolve); +} + +static void dns_found_answer(char *question, uint32_t answer) { struct pending_connection_t *pend; struct cached_resolve search; struct cached_resolve *resolve; @@ -155,8 +229,8 @@ static int dns_found_answer(char *question, uint32_t answer) { resolve = SPLAY_FIND(cache_tree, &cache_root, &search); if(!resolve) { - log(LOG_ERR,"dns_found_answer(): Answer to unasked question '%s'? Dropping.", question); - return 0; + log_fn(LOG_ERR,"Answer to unasked question '%s'? Dropping.", question); + return; } assert(resolve->state == CACHE_STATE_PENDING); @@ -166,7 +240,7 @@ static int dns_found_answer(char *question, uint32_t answer) { */ if(resolve->state != CACHE_STATE_PENDING) { log(LOG_ERR,"dns_found_answer(): BUG: resolve '%s' in state %d (not pending). Dropping.",question, resolve->state); - return 0; + return; } resolve->answer = ntohl(answer); @@ -184,7 +258,6 @@ static int dns_found_answer(char *question, uint32_t answer) { resolve->pending_connections = pend->next; free(pend); } - return 0; } /******************************************************************/ @@ -202,7 +275,8 @@ int connection_dns_process_inbuf(connection_t *conn) { if(conn->inbuf_reached_eof) { log(LOG_ERR,"connection_dnsworker_process_inbuf(): Read eof. Worker dying."); - /* XXX if the dns request is pending, go through and either repeat or mark it failed */ + if(conn->state == DNSWORKER_STATE_BUSY) + dns_cancel_pending_resolve(conn->address); return -1; } @@ -320,7 +394,14 @@ static void spawn_enough_workers(void) { /* We always want at least one worker idle. * So find the oldest busy worker and kill it. */ + dnsconn = connection_get_by_type_state_lastwritten(CONN_TYPE_DNSWORKER, DNSWORKER_STATE_BUSY); + assert(dnsconn); + + /* tell the exit connection that it's failed */ + dns_cancel_pending_resolve(dnsconn->address); + dnsconn->marked_for_close = 1; + num_workers_busy--; } if(num_workers_busy >= MIN_DNSWORKERS) @@ -328,9 +409,6 @@ static void spawn_enough_workers(void) { else num_workers_needed = MIN_DNSWORKERS; - if(num_workers_needed >= MAX_DNSWORKERS) - num_workers_needed = MAX_DNSWORKERS; - while(num_workers < num_workers_needed) { if(dns_spawn_worker() < 0) { log(LOG_ERR,"spawn_enough_workers(): spawn failed!"); |