diff --git a/trunk/src/or/connection.c b/trunk/src/or/connection.c index 26ee19527e..bb7f5e8ec9 100644 --- a/trunk/src/or/connection.c +++ b/trunk/src/or/connection.c @@ -545,7 +545,7 @@ int connection_process_inbuf(connection_t *conn) { return connection_edge_process_inbuf(conn); case CONN_TYPE_DIR: return connection_dir_process_inbuf(conn); - case CONN_TYPE_DNSMASTER: + case CONN_TYPE_DNSWORKER: return connection_dns_process_inbuf(conn); default: log(LOG_DEBUG,"connection_process_inbuf() got unexpected conn->type."); @@ -686,7 +686,7 @@ int connection_finished_flushing(connection_t *conn) { return connection_edge_finished_flushing(conn); case CONN_TYPE_DIR: return connection_dir_finished_flushing(conn); - case CONN_TYPE_DNSMASTER: + case CONN_TYPE_DNSWORKER: return connection_dns_finished_flushing(conn); default: log(LOG_DEBUG,"connection_finished_flushing() got unexpected conn->type."); diff --git a/trunk/src/or/dns.c b/trunk/src/or/dns.c index c9668f0967..562eb1caba 100644 --- a/trunk/src/or/dns.c +++ b/trunk/src/or/dns.c @@ -3,407 +3,21 @@ /* $Id$ */ #include "or.h" +#include "tree.h" #define MAX_ADDRESSLEN 256 -#define MAX_DNSSLAVES 50 -#define MIN_DNSSLAVES 3 /* 1 for the tor process, 3 slaves */ +#define MAX_DNSWORKERS 50 +#define MIN_DNSWORKERS 3 -struct slave_data_t { - int fd; /* socket to talk on */ - int num_processed; /* number of times we've used this slave */ - char busy; /* whether this slave currently has a task */ - char question[MAX_ADDRESSLEN]; /* the hostname that we're resolving */ - unsigned char question_len; /* how many bytes in question */ - char answer[MAX_ADDRESSLEN]; /* the answer to the question */ - unsigned char answer_len; /* how many bytes in answer */ -}; +int num_workers=0; +int num_workers_busy=0; -struct slave_data_t slave_data[MAX_DNSSLAVES+1]; -struct pollfd poll_data[MAX_DNSSLAVES+1]; - -static int dns_spawn_slave(void); -static int dns_read_block(int fd, char *string, unsigned char *len); -static int dns_write_block(int fd, char *string, unsigned char len); -static int dns_read_tor_question(int index); -static int dns_read_slave_response(int index); -static int dns_find_idle_slave(int max); -static int dns_assign_to_slave(int from, int to); -static int dns_master_to_tor(int from, int to); -static void dns_master_main(int fd); -static int dns_tor_to_master(connection_t *exitconn); -static int dns_found_answer(char *question, uint32_t answer, uint32_t valid); - -int connection_dns_finished_flushing(connection_t *conn) { - - assert(conn && conn->type == CONN_TYPE_DNSMASTER); - - connection_stop_writing(conn); - - return 0; -} - -int connection_dns_process_inbuf(connection_t *conn) { - unsigned char length; - char buf[MAX_ADDRESSLEN]; - char *question; - uint32_t answer; - - assert(conn && conn->type == CONN_TYPE_DNSMASTER); - assert(conn->state == DNSMASTER_STATE_OPEN); - - if(conn->inbuf_reached_eof) { - log(LOG_ERR,"connection_dns_process_inbuf(): Read eof. No more dnsmaster!"); - return -1; - } - - assert(conn->inbuf); - - if(conn->inbuf_datalen <= 0) - return 0; - - /* peek into the inbuf, so we can check if it's all here */ - length = *conn->inbuf; /* warning: abstraction violation :( */ - assert(length < 240); - - if(conn->inbuf_datalen < 1+length) { /* entire answer available? */ - log(LOG_INFO,"connection_dns_process_inbuf(): %d available, waiting for %d.", conn->inbuf_datalen, length+1); - return 0; /* not yet */ - } - - if(connection_fetch_from_buf(buf,1+length,conn) < 0) { - log(LOG_ERR,"connection_dns_process_inbuf(): Broken inbuf. No more dnsmaster!"); - return -1; - } - - question = buf+1; - log(LOG_DEBUG,"connection_dns_process_inbuf(): length %d, question '%s', strlen question %d", length, question, strlen(question)); - assert(length == 4 + strlen(question) + 1); - - answer = *(uint32_t *)(buf+1+length-4); - dns_found_answer(question, answer, (answer != 0)); - return connection_process_inbuf(conn); /* process the remainder of the buffer */ -} - -/* return -1 if error, else the fd that can talk to the dns master */ -int dns_master_start(void) { - connection_t *conn; - pid_t pid; - int fd[2]; - - if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - log(LOG_ERR,"dns_master_start(): socketpair failed."); - return -1; - } - - pid = fork(); - if(pid < 0) { - log(LOG_ERR,"dns_master_start(): fork failed."); - return -1; - } - if(pid == 0) { /* i'm the child */ - log(LOG_DEBUG,"dns_master_start(): child says fd0 %d, fd1 %d.", fd[0], fd[1]); - close(fd[0]); - dns_master_main(fd[1]); - assert(0); /* never gets here */ - } - - /* i'm the parent */ - - close(fd[1]); - - fcntl(fd[0], F_SETFL, O_NONBLOCK); /* set s to non-blocking */ - - conn = connection_new(CONN_TYPE_DNSMASTER); - if(!conn) { - log(LOG_INFO,"dns_master_start(): connection_new failed. Giving up."); - /* XXX tell the dnsmaster to die */ - return -1; - } - - conn->s = fd[0]; - conn->address = strdup("localhost"); - conn->receiver_bucket = -1; /* edge connections don't do receiver buckets */ - conn->bandwidth = -1; - - if(connection_add(conn) < 0) { /* no space, forget it */ - log(LOG_INFO,"dns_master_start(): connection_add failed. Giving up."); - connection_free(conn); - /* XXX tell the dnsmaster to die */ - return -1; - } - - conn->state = DNSMASTER_STATE_OPEN; - connection_start_reading(conn); - log(LOG_INFO,"dns_master_start(): dns handler is spawned."); - return fd[0]; -} - -static void dns_slave_main(int fd) { - char question[MAX_ADDRESSLEN]; - unsigned char question_len; - struct hostent *rent; - - for(;;) { - if(dns_read_block(fd, question, &question_len) < 0) { /* the master wants us to die */ - log(LOG_INFO,"dns_slave_main(): eof on read from master. Exiting."); - exit(0); - } - - rent = gethostbyname(question); - if (!rent) { - log(LOG_INFO,"dns_slave_main(): Could not resolve dest addr %s. Returning nulls.",question); - if(dns_write_block(fd, "\0\0\0\0", 4) < 0) { - log(LOG_INFO,"dns_slave_main(): writing to master failed. Exiting."); - exit(0); - } - } else { - if(dns_write_block(fd, rent->h_addr, rent->h_length) < 0) { - log(LOG_INFO,"dns_slave_main(): writing to master failed. Exiting."); - exit(0); - } - log(LOG_INFO,"dns_slave_main(): Answered question '%s'.",question); - } - } -} - -static int dns_spawn_slave(void) { - pid_t pid; - int fd[2]; - - if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { - perror("socketpair"); - exit(1); - } - - pid = fork(); - if(pid < 0) { - perror("fork"); - exit(1); - } - if(pid == 0) { /* i'm the child */ - close(fd[0]); - dns_slave_main(fd[1]); - assert(0); /* never gets here */ - } - - /* i'm the parent */ - log(LOG_INFO,"dns_spawn_slave(): just spawned a slave."); // XXX change to debug - close(fd[1]); - return fd[0]; -} - -/* read a first byte from fd, put it into *len. Then read *len - * bytes from fd and put it into string. - * Return -1 if eof or read error or bad len, else return 0. - */ -int dns_read_block(int fd, char *string, unsigned char *len) { - int read_result; - - log(LOG_DEBUG,"dns_read_block(): Calling read to learn length (fd %d).", fd); - read_result = read(fd, len, 1); - log(LOG_DEBUG,"dns_read_block(): read finished, returned %d", read_result); - if (read_result < 0) { - log(LOG_INFO,"dns_read_block(): read len returned error"); - return -1; - } else if (read_result == 0) { - log(LOG_INFO,"dns_read_block(): Encountered eof reading len"); - return -1; - } else if (*len <= 0) { - log(LOG_INFO,"dns_read_block(): len not >0"); - return -1; - } - - log(LOG_DEBUG,"dns_read_block(): Calling read to get string, length %u.", *len); - read_result = read(fd, string, *len); - if (read_result < 0) { - log(LOG_INFO,"dns_read_block(): read string returned error"); - return -1; - } else if (read_result == 0) { - log(LOG_INFO,"dns_read_block(): Encountered eof reading string"); - return -1; - } - - string[*len] = 0; /* null terminate it, just in case */ -// log(LOG_INFO,"dns_read_block(): Read '%s', len %u.",string,*len); - return 0; -} - -/* write ("%c%s", string, len) onto fd */ -static int dns_write_block(int fd, char *string, unsigned char len) { - int write_result; - int written=0; - char tmp[257]; - - assert(len <= 250); - tmp[0] = len; - memcpy(tmp+1, string, len); - log(LOG_DEBUG,"dns_write_block(): writing length %u, fd %d.", len, fd); - - while(written < len+1) { - write_result = write(fd, tmp, len+1-written); - if (write_result < 0) { - return -1; - } - written += write_result; - } - - return 0; -} - -/* pull in question. block until we've read everything. - * return -1 if eof. */ -static int dns_read_tor_question(int index) { - - log(LOG_DEBUG,"dns_read_tor_question(): Pulling question from tor"); - if(dns_read_block(slave_data[index].fd, - slave_data[index].question, - &slave_data[index].question_len) < 0) - return -1; - -// log(LOG_INFO,"dns_read_tor_question(): Read question '%s'",slave_data[index].question); - return 0; -} - -/* pull in answer. block until we've read it. return -1 if eof. */ -static int dns_read_slave_response(int index) { - - if(dns_read_block(slave_data[index].fd, - slave_data[index].answer, - &slave_data[index].answer_len) < 0) - return -1; - - return 0; -} - -static int dns_find_idle_slave(int max) { - int i; - - for(i=1;i= 0) { - while(1) { - idle = dns_find_idle_slave(nfds); - if(dns_assign_to_slave(i, idle) >= 0) - break; /* successfully assigned to one */ - /* XXX slave must die, recalc num slaves and num busy */ - } - num_slaves_busy++; - } else { /* error */ - log(LOG_INFO,"dns_master_main(): dns_read_tor_question failed. Master dying."); - exit(1); - } - } else { - if(dns_read_slave_response(i) >= 0) { - if(dns_master_to_tor(i, 0) < 0) { - log(LOG_INFO,"dns_master_main(): dns_master_to_tor failed. Master dying."); - exit(1); - } - slave_data[i].busy = 0; - num_slaves_busy--; - poll_data[0].events = POLLIN; /* resume reading from tor if we'd stopped */ - } else { /* error */ - log(LOG_INFO,"dns_master_main(): dns_read_slave_response failed. Leaving slave stranded (FIXME)"); - } - } - } - } - log(LOG_DEBUG,"dns_master_main(): Finished looping over fd's."); - - if(num_slaves_busy >= num_slaves_needed) { - if(num_slaves_needed == MAX_DNSSLAVES-1) - poll_data[0].events = 0; /* stop reading from tor */ - else - num_slaves_needed++; - } - - } - assert(0); /* should never get here */ -} - - - -#include "tree.h" +static int dns_assign_to_worker(connection_t *exitconn); +static int 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); struct pending_connection_t { struct connection_t *conn; @@ -433,14 +47,18 @@ static int compare_cached_resolves(struct cached_resolve *a, struct cached_resol SPLAY_PROTOTYPE(cache_tree, cached_resolve, node, compare_cached_resolves); SPLAY_GENERATE(cache_tree, cached_resolve, node, compare_cached_resolves); -void init_cache_tree(void) { +static void init_cache_tree(void) { SPLAY_INIT(&cache_root); } +void dns_init(void) { + init_cache_tree(); + spawn_enough_workers(); +} -/* see if the question 'exitconn->address' has been answered. if so, - * if resolve valid, put it into exitconn->addr and call - * connection_exit_connect directly. If resolve failed, return -1. +/* 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. * * Else, if seen before and pending, add conn to the pending list, * and return 0. @@ -485,50 +103,51 @@ int dns_resolve(connection_t *exitconn) { resolve->pending_connections = pending_connection; SPLAY_INSERT(cache_tree, &cache_root, resolve); - return dns_tor_to_master(exitconn); + return dns_assign_to_worker(exitconn); } assert(0); return 0; /* not reached; keep gcc happy */ } -static int dns_tor_to_master(connection_t *exitconn) { +static int dns_assign_to_worker(connection_t *exitconn) { connection_t *dnsconn; unsigned char len; - int do_dns_directly=0; + struct hostent *rent; - dnsconn = connection_get_by_type(CONN_TYPE_DNSMASTER); + spawn_enough_workers(); /* respawn here, to be sure there are enough */ + + dnsconn = connection_get_by_type_state(CONN_TYPE_DNSWORKER, DNSWORKER_STATE_IDLE); if(!dnsconn) { - log(LOG_ERR,"dns_tor_to_master(): dns master nowhere to be found!"); - } + log(LOG_INFO,"dns_assign_to_worker(): no idle dns workers. Doing it myself."); - if(!dnsconn || do_dns_directly) { - /* new version which does it all right here */ - struct hostent *rent; + /* short version which does it all right here */ rent = gethostbyname(exitconn->address); if (!rent) { - return dns_found_answer(exitconn->address, 0, 0); + return dns_found_answer(exitconn->address, 0); } - return dns_found_answer(exitconn->address, *(uint32_t *)rent->h_addr, 1); + return dns_found_answer(exitconn->address, *(uint32_t *)rent->h_addr); } - len = strlen(exitconn->address); - if(connection_write_to_buf(&len, 1, dnsconn) < 0) { - log(LOG_DEBUG,"dns_tor_to_master(): Couldn't write length."); + dnsconn->address = strdup(exitconn->address); + dnsconn->state = DNSWORKER_STATE_BUSY; + num_workers_busy++; + + len = strlen(dnsconn->address); + /* FFFF we should have it retry if the first worker bombs out */ + if(connection_write_to_buf(&len, 1, dnsconn) < 0 || + 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; return -1; } - if(connection_write_to_buf(exitconn->address, len, dnsconn) < 0) { - log(LOG_DEBUG,"dns_tor_to_master(): Couldn't write address."); - return -1; - } - -// log(LOG_DEBUG,"dns_tor_to_master(): submitted '%s'", address); +// log(LOG_DEBUG,"dns_assign_to_worker(): submitted '%s'", exitconn->address); return 0; } -static int dns_found_answer(char *question, uint32_t answer, uint32_t valid) { +static int dns_found_answer(char *question, uint32_t answer) { struct pending_connection_t *pend; struct cached_resolve search; struct cached_resolve *resolve; @@ -541,7 +160,7 @@ static int dns_found_answer(char *question, uint32_t answer, uint32_t valid) { return 0; } -// assert(resolve->state == CACHE_STATE_PENDING); + assert(resolve->state == CACHE_STATE_PENDING); /* XXX this is a bug which hasn't been found yet. Probably something * about slaves answering questions when they're not supposed to, and * reusing the old question. @@ -552,7 +171,7 @@ static int dns_found_answer(char *question, uint32_t answer, uint32_t valid) { } resolve->answer = ntohl(answer); - if(valid) + if(resolve->answer) resolve->state = CACHE_STATE_VALID; else resolve->state = CACHE_STATE_FAILED; @@ -569,6 +188,157 @@ static int dns_found_answer(char *question, uint32_t answer, uint32_t valid) { return 0; } +/******************************************************************/ + +int connection_dns_finished_flushing(connection_t *conn) { + assert(conn && conn->type == CONN_TYPE_DNSWORKER); + connection_stop_writing(conn); + return 0; +} + +int connection_dns_process_inbuf(connection_t *conn) { + uint32_t answer; + + assert(conn && conn->type == CONN_TYPE_DNSWORKER); + + 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 */ + return -1; + } + + assert(conn->state == DNSWORKER_STATE_BUSY); + if(conn->inbuf_datalen < 4) /* entire answer available? */ + return 0; /* not yet */ + assert(conn->inbuf_datalen == 4); + + if(connection_fetch_from_buf((char*)&answer,sizeof(answer),conn) < 0) { + log(LOG_ERR,"connection_dnsworker_process_inbuf(): Broken inbuf. Worker dying."); + /* XXX exitconn's never going to get his answer :( */ + return -1; + } + + dns_found_answer(conn->address, answer); + + free(conn->address); + conn->address = NULL; + conn->state = DNSWORKER_STATE_IDLE; + num_workers_busy--; + + return 0; +} + +static void dnsworker_main(int fd) { + char question[MAX_ADDRESSLEN]; + unsigned char question_len; + struct hostent *rent; + + for(;;) { + + if(read(fd, &question_len, 1) != 1) { + log(LOG_INFO,"dnsworker_main(): read length failed. Exiting."); + exit(0); + } + assert(question_len > 0); + + if(read(fd, question, question_len) != question_len) { + log(LOG_INFO,"dnsworker_main(): read hostname failed. Exiting."); + exit(0); + } + question[question_len] = 0; /* null terminate it */ + + rent = gethostbyname(question); + if (!rent) { + log(LOG_INFO,"dnsworker_main(): Could not resolve dest addr %s. Returning nulls.",question); + /* XXX it's conceivable write could return 1 through 3. but that's never gonna happen, right? */ + if(write(fd, "\0\0\0\0", 4) != 4) { + log(LOG_INFO,"dnsworker_main(): writing nulls failed. Exiting."); + exit(0); + } + } else { + assert(rent->h_length == 4); /* break to remind us if we move away from ipv4 */ + if(write(fd, rent->h_addr, 4) != 4) { + log(LOG_INFO,"dnsworker_main(): writing answer failed. Exiting."); + exit(0); + } + log(LOG_INFO,"dnsworker_main(): Answered question '%s'.",question); + } + } +} + +static int dns_spawn_worker(void) { + pid_t pid; + int fd[2]; + connection_t *conn; + + if(socketpair(AF_UNIX, SOCK_STREAM, 0, fd) < 0) { + perror("socketpair"); + exit(1); + } + + pid = fork(); + if(pid < 0) { + perror("fork"); + exit(1); + } + if(pid == 0) { /* i'm the child */ + close(fd[0]); + dnsworker_main(fd[1]); + assert(0); /* never gets here */ + } + + /* i'm the parent */ + log(LOG_DEBUG,"dns_spawn_worker(): just spawned a worker."); + close(fd[1]); + + conn = connection_new(CONN_TYPE_DNSWORKER); + if(!conn) { + close(fd[0]); + return -1; + } + + fcntl(fd[0], F_SETFL, O_NONBLOCK); /* set it to non-blocking */ + + /* set up conn so it's got all the data we need to remember */ + conn->receiver_bucket = -1; /* non-cell connections don't do receiver buckets */ + conn->bandwidth = -1; + conn->s = fd[0]; + + if(connection_add(conn) < 0) { /* no space, forget it */ + log(LOG_INFO,"dns_spawn_worker(): connection_add failed. Giving up."); + connection_free(conn); /* this closes fd[0] */ + return -1; + } + + conn->state = DNSWORKER_STATE_IDLE; + connection_start_reading(conn); + + return 0; /* success */ +} + +static void spawn_enough_workers(void) { + int num_workers_needed; /* aim to have 1 more than needed, + * but no less than min and no more than max */ + + if(num_workers_busy >= MIN_DNSWORKERS) + num_workers_needed = num_workers_busy+1; + 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!"); + return; + } + num_workers++; + } + + /* FFFF this is where we will cull extra workers */ +} + /* Local Variables: mode:c diff --git a/trunk/src/or/main.c b/trunk/src/or/main.c index 99d827675e..c231d57d68 100644 --- a/trunk/src/or/main.c +++ b/trunk/src/or/main.c @@ -168,6 +168,18 @@ connection_t *connection_get_by_type(int type) { return NULL; } +connection_t *connection_get_by_type_state(int type, int state) { + int i; + connection_t *conn; + + for(i=0;itype == type && conn->state == state) + return conn; + } + return NULL; +} + void connection_watch_events(connection_t *conn, short events) { assert(conn && conn->poll_index < nfds); @@ -782,17 +794,13 @@ int tor_main(int argc, char *argv[]) { exit(1); log(options.loglevel,NULL); /* assign logging severity level from options */ - if (options.Daemon) + if(options.Daemon) daemonize(); if(options.OnionRouter) { /* only spawn dns handlers if we're a router */ - if(dns_master_start() < 0) { - log(LOG_ERR,"main(): We're running without a dns handler. Bad news."); - } + dns_init(); /* initialize the dns resolve tree, and spawn workers */ } - init_cache_tree(); /* initialize the dns resolve tree */ - signal (SIGINT, catch); /* catch kills so we can exit cleanly */ signal (SIGTERM, catch); signal (SIGUSR1, catch); /* to dump stats to stdout */ diff --git a/trunk/src/or/or.h b/trunk/src/or/or.h index 80392bcf1a..3a75f29eaf 100644 --- a/trunk/src/or/or.h +++ b/trunk/src/or/or.h @@ -62,11 +62,12 @@ #define CONN_TYPE_AP 7 #define CONN_TYPE_DIR_LISTENER 8 #define CONN_TYPE_DIR 9 -#define CONN_TYPE_DNSMASTER 10 +#define CONN_TYPE_DNSWORKER 10 #define LISTENER_STATE_READY 0 -#define DNSMASTER_STATE_OPEN 0 +#define DNSWORKER_STATE_IDLE 0 +#define DNSWORKER_STATE_BUSY 1 /* how to read these states: * foo_CONN_STATE_bar_baz: @@ -83,7 +84,7 @@ #define OR_CONN_STATE_SERVER_NONCE_WAIT 8 /* waiting for confirmation of nonce */ #define OR_CONN_STATE_OPEN 9 /* ready to send/receive cells. */ -#define EXIT_CONN_STATE_RESOLVING 0 /* waiting for response from dnsmaster */ +#define EXIT_CONN_STATE_RESOLVING 0 /* waiting for response from dns farm */ #define EXIT_CONN_STATE_CONNECTING 1 /* waiting for connect() to finish */ #define EXIT_CONN_STATE_OPEN 2 #if 0 @@ -267,6 +268,9 @@ struct connection_t { /* Used while negotiating OR/OR connections */ char nonce[8]; + +/* Used by worker connections */ + int num_processed; }; @@ -646,7 +650,7 @@ int connection_dir_handle_listener_read(connection_t *conn); int connection_dns_finished_flushing(connection_t *conn); int connection_dns_process_inbuf(connection_t *conn); -void init_cache_tree(void); +void dns_init(void); int dns_resolve(connection_t *exitconn); int dns_master_start(void); @@ -664,6 +668,7 @@ connection_t *connection_twin_get_by_addr_port(uint32_t addr, uint16_t port); connection_t *connection_exact_get_by_addr_port(uint32_t addr, uint16_t port); connection_t *connection_get_by_type(int type); +connection_t *connection_get_by_type_state(int type, int state); void connection_watch_events(connection_t *conn, short events); void connection_stop_reading(connection_t *conn);