From 2d3ac08633a47617c1244caab8b41f73604e0d48 Mon Sep 17 00:00:00 2001 From: Roger Dingledine Date: Tue, 30 Mar 2004 22:57:49 +0000 Subject: [PATCH] Refactor directory servers * read all the time (before we would ignore eof sometimes, oops) * we can handle different urls now * send back 404 for an un-handled url * commands initiated by the client can handle payloads now * introduce conn->purpose to avoid exponential state-space explosion svn:r1400 --- src/or/connection.c | 22 ++-- src/or/directory.c | 263 ++++++++++++++++++++++++++++++-------------- src/or/main.c | 5 +- src/or/or.h | 26 +++-- src/or/router.c | 6 +- 5 files changed, 218 insertions(+), 104 deletions(-) diff --git a/src/or/connection.c b/src/or/connection.c index 583a969572..b6ed726c3e 100644 --- a/src/or/connection.c +++ b/src/or/connection.c @@ -49,14 +49,11 @@ char *conn_state_to_string[][_CONN_TYPE_MAX+1] = { "open" }, /* 8 */ { "ready" }, /* dir listener, 0 */ { "", /* dir, 0 */ - "connecting (fetch)", /* 1 */ - "connecting (upload)", /* 2 */ - "client sending fetch", /* 3 */ - "client sending upload", /* 4 */ - "client reading fetch", /* 5 */ - "client reading upload", /* 6 */ - "awaiting command", /* 7 */ - "writing" }, /* 8 */ + "connecting", /* 1 */ + "client sending", /* 2 */ + "client reading", /* 3 */ + "awaiting command", /* 4 */ + "writing" }, /* 5 */ { "", /* dns worker, 0 */ "idle", /* 1 */ "busy" }, /* 2 */ @@ -373,6 +370,7 @@ static int connection_init_accepted_conn(connection_t *conn) { conn->state = AP_CONN_STATE_SOCKS_WAIT; break; case CONN_TYPE_DIR: + conn->purpose = DIR_PURPOSE_SERVER; conn->state = DIR_CONN_STATE_SERVER_COMMAND_WAIT; break; } @@ -599,8 +597,7 @@ int connection_handle_read(connection_t *conn) { if(connection_read_to_buf(conn) < 0) { if(conn->type == CONN_TYPE_DIR && - (conn->state == DIR_CONN_STATE_CONNECTING_FETCH || - conn->state == DIR_CONN_STATE_CONNECTING_UPLOAD)) { + conn->state == DIR_CONN_STATE_CONNECTING) { /* it's a directory server and connecting failed: forget about this router */ /* XXX I suspect pollerr may make Windows not get to this point. :( */ router_mark_as_down(conn->nickname); @@ -1029,6 +1026,9 @@ void assert_connection_ok(connection_t *conn, time_t now) } else { assert(!conn->socks_request); } + if(conn->type != CONN_TYPE_DIR) { + assert(!conn->purpose); /* only used for dir types currently */ + } switch(conn->type) { @@ -1053,6 +1053,8 @@ void assert_connection_ok(connection_t *conn, time_t now) case CONN_TYPE_DIR: assert(conn->state >= _DIR_CONN_STATE_MIN && conn->state <= _DIR_CONN_STATE_MAX); + assert(conn->purpose >= _DIR_PURPOSE_MIN && + conn->purpose <= _DIR_PURPOSE_MAX); break; case CONN_TYPE_DNSWORKER: assert(conn->state == DNSWORKER_STATE_IDLE || diff --git a/src/or/directory.c b/src/or/directory.c index c73d4d1e7f..941ad4d563 100644 --- a/src/or/directory.c +++ b/src/or/directory.c @@ -4,7 +4,8 @@ #include "or.h" -static int directory_send_command(connection_t *conn, int command); +static void directory_send_command(connection_t *conn, + int purpose, const char *payload); static int directory_handle_command(connection_t *conn); /********* START VARIABLES **********/ @@ -17,20 +18,26 @@ extern int has_fetched_directory; /********* END VARIABLES ************/ -void directory_initiate_command(routerinfo_t *router, int command) { +void directory_initiate_command(routerinfo_t *router, int purpose, const char *payload) { connection_t *conn; - switch(command) { - case DIR_CONN_STATE_CONNECTING_FETCH: + switch(purpose) { + case DIR_PURPOSE_FETCH_DIR: log_fn(LOG_DEBUG,"initiating directory fetch"); break; - case DIR_CONN_STATE_CONNECTING_UPLOAD: - log_fn(LOG_DEBUG,"initiating directory upload"); + case DIR_PURPOSE_FETCH_HIDSERV: + log_fn(LOG_DEBUG,"initiating hidden-service descriptor fetch"); + break; + case DIR_PURPOSE_UPLOAD_DIR: + log_fn(LOG_DEBUG,"initiating server descriptor upload"); + break; + case DIR_PURPOSE_UPLOAD_HIDSERV: + log_fn(LOG_DEBUG,"initiating hidden-service descriptor upload"); break; } if (!router) { /* i guess they didn't have one in mind for me to use */ - log_fn(LOG_WARN,"No running dirservers known. Not trying."); + log_fn(LOG_WARN,"No running dirservers known. Not trying. (purpose %d)", purpose); return; } @@ -44,57 +51,99 @@ void directory_initiate_command(routerinfo_t *router, int command) { assert(router->identity_pkey); conn->identity_pkey = crypto_pk_dup_key(router->identity_pkey); - conn->state = command; + conn->purpose = purpose; if(connection_add(conn) < 0) { /* no space, forget it */ connection_free(conn); return; } - switch(connection_connect(conn, router->address, router->addr, router->dir_port)) { - case -1: - router_mark_as_down(conn->nickname); /* don't try him again */ - connection_mark_for_close(conn, 0); - return; - case 0: - connection_set_poll_socket(conn); - connection_watch_events(conn, POLLIN | POLLOUT | POLLERR); - /* writable indicates finish, readable indicates broken link, - error indicates broken link in windowsland. */ - return; - /* case 1: fall through */ - } + /* queue the command on the outbuf */ + directory_send_command(conn, purpose, payload); - connection_set_poll_socket(conn); - if(directory_send_command(conn, command) < 0) { - connection_mark_for_close(conn, 0); + if(purpose == DIR_PURPOSE_FETCH_DIR || + purpose == DIR_PURPOSE_UPLOAD_DIR) { + + /* then we want to connect directly */ + conn->state = DIR_CONN_STATE_CONNECTING; + + switch(connection_connect(conn, conn->address, conn->addr, conn->port)) { + case -1: + router_mark_as_down(conn->nickname); /* don't try him again */ + connection_mark_for_close(conn, 0); + return; + case 1: + conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ + /* fall through */ + case 0: + connection_set_poll_socket(conn); + connection_watch_events(conn, POLLIN | POLLOUT | POLLERR); + /* writable indicates finish, readable indicates broken link, + error indicates broken link in windowsland. */ + } + } else { /* we want to connect via tor */ + /* make an AP connection + * populate it and add it at the right state + * socketpair and hook up both sides + */ + + conn->state = DIR_CONN_STATE_CLIENT_SENDING; + connection_set_poll_socket(conn); } } -static int directory_send_command(connection_t *conn, int command) { +static void directory_send_command(connection_t *conn, + int purpose, const char *payload) { char fetchstring[] = "GET / HTTP/1.0\r\n\r\n"; - const char *s; char tmp[8192]; assert(conn && conn->type == CONN_TYPE_DIR); - switch(command) { - case DIR_CONN_STATE_CONNECTING_FETCH: + switch(purpose) { + case DIR_PURPOSE_FETCH_DIR: + assert(payload == NULL); connection_write_to_buf(fetchstring, strlen(fetchstring), conn); - conn->state = DIR_CONN_STATE_CLIENT_SENDING_FETCH; break; - case DIR_CONN_STATE_CONNECTING_UPLOAD: - s = router_get_my_descriptor(); - if(!s) { - log_fn(LOG_WARN,"Failed to get my descriptor."); - return -1; - } + case DIR_PURPOSE_UPLOAD_DIR: + assert(payload); snprintf(tmp, sizeof(tmp), "POST / HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s", - (int)strlen(s), s); + (int)strlen(payload), payload); + connection_write_to_buf(tmp, strlen(tmp), conn); + break; + case DIR_PURPOSE_FETCH_HIDSERV: + assert(payload); + snprintf(tmp, sizeof(tmp), "GET /hidserv/%s HTTP/1.0\r\n\r\n", payload); + connection_write_to_buf(tmp, strlen(tmp), conn); + break; + case DIR_PURPOSE_UPLOAD_HIDSERV: + assert(payload); + snprintf(tmp, sizeof(tmp), + "POST /hidserv/ HTTP/1.0\r\nContent-Length: %d\r\n\r\n%s", + (int)strlen(payload), payload); connection_write_to_buf(tmp, strlen(tmp), conn); - conn->state = DIR_CONN_STATE_CLIENT_SENDING_UPLOAD; break; } +} + +/* Parse "%s %s HTTP/1..." + * If it's well-formed, point *url to the second %s, + * null-terminate it (this modifies headers!) and return 0. + * Otherwise, return -1. + */ +int parse_http_url(char *headers, char **url) { + char *s, *tmp; + + s = (char *)eat_whitespace_no_nl(headers); + if (!*s) return -1; + s = (char *)find_whitespace(s); /* get past GET/POST */ + if (!*s) return -1; + s = (char *)eat_whitespace_no_nl(s); + if (!*s) return -1; + tmp = s; /* this is it, assuming it's valid */ + s = (char *)find_whitespace(s); + if (!*s) return -1; + *s = 0; + *url = tmp; return 0; } @@ -131,8 +180,7 @@ int connection_dir_process_inbuf(connection_t *conn) { assert(conn && conn->type == CONN_TYPE_DIR); if(conn->inbuf_reached_eof) { - if(conn->state != DIR_CONN_STATE_CLIENT_READING_FETCH && - conn->state != DIR_CONN_STATE_CLIENT_READING_UPLOAD) { + if(conn->state != DIR_CONN_STATE_CLIENT_READING) { log_fn(LOG_INFO,"conn reached eof, not reading. Closing."); connection_close_immediate(conn); /* it was an error; give up on flushing */ connection_mark_for_close(conn,0); @@ -160,7 +208,7 @@ int connection_dir_process_inbuf(connection_t *conn) { return -1; } - if(conn->state == DIR_CONN_STATE_CLIENT_READING_FETCH) { + if(conn->purpose == DIR_PURPOSE_FETCH_DIR) { /* fetch/process the directory to learn about new routers. */ int directorylen; directorylen = strlen(directory); @@ -192,7 +240,7 @@ int connection_dir_process_inbuf(connection_t *conn) { return 0; } - if(conn->state == DIR_CONN_STATE_CLIENT_READING_UPLOAD) { + if(conn->purpose == DIR_PURPOSE_UPLOAD_DIR) { switch(status_code) { case 200: log_fn(LOG_INFO,"eof (status 200) while reading upload response: finished."); @@ -203,16 +251,36 @@ int connection_dir_process_inbuf(connection_t *conn) { case 403: log_fn(LOG_WARN,"http status 403 (unapproved server) response from dirserver. Is your clock skewed? Have you mailed arma your identity fingerprint? Are you using the right key?"); + break; + default: + log_fn(LOG_WARN,"http status %d response unrecognized.", status_code); break; } free(directory); free(headers); connection_mark_for_close(conn,0); return 0; } + + if(conn->purpose == DIR_PURPOSE_FETCH_HIDSERV) { + + + } + + if(conn->purpose == DIR_PURPOSE_UPLOAD_HIDSERV) { + + + } + assert(0); /* never reached */ } - if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) - return directory_handle_command(conn); + if(conn->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT) { + if (directory_handle_command(conn) < 0) { + connection_mark_for_close(conn,0); + return -1; + } else { + return 0; + } + } /* XXX for READ states, might want to make sure inbuf isn't too big */ @@ -223,54 +291,94 @@ int connection_dir_process_inbuf(connection_t *conn) { static char answer200[] = "HTTP/1.0 200 OK\r\n\r\n"; static char answer400[] = "HTTP/1.0 400 Bad request\r\n\r\n"; static char answer403[] = "HTTP/1.0 403 Unapproved server\r\n\r\n"; +static char answer404[] = "HTTP/1.0 404 Not found\r\n\r\n"; static char answer503[] = "HTTP/1.0 503 Directory unavailable\r\n\r\n"; +/* always returns 0 */ static int directory_handle_command_get(connection_t *conn, char *headers, char *body) { size_t dlen; const char *cp; + char *url; - /* XXX should check url and http version */ log_fn(LOG_DEBUG,"Received GET command."); - dlen = dirserv_get_directory(&cp); + conn->state = DIR_CONN_STATE_SERVER_WRITING; - if(dlen == 0) { - log_fn(LOG_WARN,"My directory is empty. Closing."); - connection_write_to_buf(answer503, strlen(answer503), conn); - conn->state = DIR_CONN_STATE_SERVER_WRITING; + if (parse_http_url(headers, &url) < 0) { + connection_write_to_buf(answer400, strlen(answer400), conn); return 0; } - log_fn(LOG_DEBUG,"Dumping directory to client."); - connection_write_to_buf(answer200, strlen(answer200), conn); - connection_write_to_buf(cp, dlen, conn); - conn->state = DIR_CONN_STATE_SERVER_WRITING; + if(!strcmp(url,"/")) { /* directory fetch */ + dlen = dirserv_get_directory(&cp); + + if(dlen == 0) { + log_fn(LOG_WARN,"My directory is empty. Closing."); + connection_write_to_buf(answer503, strlen(answer503), conn); + return 0; + } + + log_fn(LOG_DEBUG,"Dumping directory to client."); + connection_write_to_buf(answer200, strlen(answer200), conn); + connection_write_to_buf(cp, dlen, conn); + return 0; + } + + if(!strncmp(url,"/hidserv/",9)) { /* hidserv descriptor fetch */ + /* ask back-end for the hidden-services descriptor in + * url+9, and return it with a 200 if valid, or give a 404 + * otherwise + */ + + } + + /* we didn't recognize the url */ + connection_write_to_buf(answer404, strlen(answer404), conn); return 0; } +/* always returns 0 */ static int directory_handle_command_post(connection_t *conn, char *headers, char *body) { const char *cp; + char *url; - /* XXX should check url and http version */ log_fn(LOG_DEBUG,"Received POST command."); - cp = body; - switch(dirserv_add_descriptor(&cp)) { - case -1: - /* malformed descriptor, or something wrong */ - connection_write_to_buf(answer400, strlen(answer400), conn); - break; - case 0: - /* descriptor was well-formed but server has not been approved */ - connection_write_to_buf(answer403, strlen(answer403), conn); - break; - case 1: - dirserv_get_directory(&cp); /* rebuild and write to disk */ - connection_write_to_buf(answer200, strlen(answer200), conn); - break; - } + conn->state = DIR_CONN_STATE_SERVER_WRITING; + + if (parse_http_url(headers, &url) < 0) { + connection_write_to_buf(answer400, strlen(answer400), conn); + return 0; + } + + if(!strcmp(url,"/")) { /* server descriptor post */ + cp = body; + switch(dirserv_add_descriptor(&cp)) { + case -1: + /* malformed descriptor, or something wrong */ + connection_write_to_buf(answer400, strlen(answer400), conn); + break; + case 0: + /* descriptor was well-formed but server has not been approved */ + connection_write_to_buf(answer403, strlen(answer403), conn); + break; + case 1: + dirserv_get_directory(&cp); /* rebuild and write to disk */ + connection_write_to_buf(answer200, strlen(answer200), conn); + break; + } + } + + if(!strncmp(url,"/hidserv/",9)) { /* hidserv descriptor post */ + /* pass 'body' to the backend */ + /* return 400, 403, or 200 as appropriate */ + + } + + /* we didn't recognize the url */ + connection_write_to_buf(answer404, strlen(answer404), conn); return 0; } @@ -312,8 +420,7 @@ int connection_dir_finished_flushing(connection_t *conn) { assert(conn && conn->type == CONN_TYPE_DIR); switch(conn->state) { - case DIR_CONN_STATE_CONNECTING_FETCH: - case DIR_CONN_STATE_CONNECTING_UPLOAD: + case DIR_CONN_STATE_CONNECTING: if (getsockopt(conn->s, SOL_SOCKET, SO_ERROR, (void*)&e, &len) < 0) { /* not yet */ if(!ERRNO_CONN_EINPROGRESS(errno)) { log_fn(LOG_DEBUG,"in-progress connect failed. Removing."); @@ -329,16 +436,12 @@ int connection_dir_finished_flushing(connection_t *conn) { log_fn(LOG_INFO,"Dir connection to router %s:%u established.", conn->address,conn->port); - return directory_send_command(conn, conn->state); - case DIR_CONN_STATE_CLIENT_SENDING_FETCH: - log_fn(LOG_DEBUG,"client finished sending fetch command."); - conn->state = DIR_CONN_STATE_CLIENT_READING_FETCH; - connection_watch_events(conn, POLLIN); + conn->state = DIR_CONN_STATE_CLIENT_SENDING; /* start flushing conn */ return 0; - case DIR_CONN_STATE_CLIENT_SENDING_UPLOAD: - log_fn(LOG_DEBUG,"client finished sending upload command."); - conn->state = DIR_CONN_STATE_CLIENT_READING_UPLOAD; - connection_watch_events(conn, POLLIN); + case DIR_CONN_STATE_CLIENT_SENDING: + log_fn(LOG_DEBUG,"client finished sending command."); + conn->state = DIR_CONN_STATE_CLIENT_READING; + connection_stop_writing(conn); return 0; case DIR_CONN_STATE_SERVER_WRITING: log_fn(LOG_INFO,"Finished writing server response. Closing."); diff --git a/src/or/main.c b/src/or/main.c index 0f0b40fc8d..eab26058f2 100644 --- a/src/or/main.c +++ b/src/or/main.c @@ -329,7 +329,7 @@ static void run_scheduled_events(time_t now) { /* NOTE directory servers do not currently fetch directories. * Hope this doesn't bite us later. */ directory_initiate_command(router_pick_directory_server(), - DIR_CONN_STATE_CONNECTING_FETCH); + DIR_PURPOSE_FETCH_DIR, NULL); } else { /* We're a directory; dump any old descriptors. */ dirserv_remove_old_servers(); @@ -519,7 +519,8 @@ static int do_hup(void) { } } else { /* fetch a new directory */ - directory_initiate_command(router_pick_directory_server(), DIR_CONN_STATE_CONNECTING_FETCH); + directory_initiate_command(router_pick_directory_server(), + DIR_PURPOSE_FETCH_DIR, NULL); } if(options.ORPort) { router_rebuild_descriptor(); diff --git a/src/or/or.h b/src/or/or.h index 4aaddafc43..abdaf114de 100644 --- a/src/or/or.h +++ b/src/or/or.h @@ -171,15 +171,20 @@ #define _AP_CONN_STATE_MAX 8 #define _DIR_CONN_STATE_MIN 1 -#define DIR_CONN_STATE_CONNECTING_FETCH 1 -#define DIR_CONN_STATE_CONNECTING_UPLOAD 2 -#define DIR_CONN_STATE_CLIENT_SENDING_FETCH 3 -#define DIR_CONN_STATE_CLIENT_SENDING_UPLOAD 4 -#define DIR_CONN_STATE_CLIENT_READING_FETCH 5 -#define DIR_CONN_STATE_CLIENT_READING_UPLOAD 6 -#define DIR_CONN_STATE_SERVER_COMMAND_WAIT 7 -#define DIR_CONN_STATE_SERVER_WRITING 8 -#define _DIR_CONN_STATE_MAX 8 +#define DIR_CONN_STATE_CONNECTING 1 +#define DIR_CONN_STATE_CLIENT_SENDING 2 +#define DIR_CONN_STATE_CLIENT_READING 3 +#define DIR_CONN_STATE_SERVER_COMMAND_WAIT 4 +#define DIR_CONN_STATE_SERVER_WRITING 5 +#define _DIR_CONN_STATE_MAX 5 + +#define _DIR_PURPOSE_MIN 1 +#define DIR_PURPOSE_FETCH_DIR 1 +#define DIR_PURPOSE_FETCH_HIDSERV 2 +#define DIR_PURPOSE_UPLOAD_DIR 3 +#define DIR_PURPOSE_UPLOAD_HIDSERV 4 +#define DIR_PURPOSE_SERVER 5 +#define _DIR_PURPOSE_MAX 5 #define CIRCUIT_STATE_BUILDING 0 /* I'm the OP, still haven't done all my handshakes */ #define CIRCUIT_STATE_ONIONSKIN_PENDING 1 /* waiting to process the onionskin */ @@ -331,6 +336,7 @@ struct connection_t { uint8_t type; uint8_t state; + uint8_t purpose; /* only used for DIR types currently */ uint8_t wants_to_read; /* should we start reading again once * the bandwidth throttler allows it? */ @@ -830,7 +836,7 @@ int assign_to_cpuworker(connection_t *cpuworker, unsigned char question_type, /********************************* directory.c ***************************/ -void directory_initiate_command(routerinfo_t *router, int command); +void directory_initiate_command(routerinfo_t *router, int purpose, const char *payload); int connection_dir_process_inbuf(connection_t *conn); int connection_dir_finished_flushing(connection_t *conn); diff --git a/src/or/router.c b/src/or/router.c index db2a7f3b1f..fe1cc070c8 100644 --- a/src/or/router.c +++ b/src/or/router.c @@ -236,12 +236,14 @@ void router_upload_desc_to_dirservers(void) { int i; routerinfo_t *router; routerlist_t *rl; + const char *s; router_get_routerlist(&rl); if(!rl) return; - if (!router_get_my_descriptor()) { + s = router_get_my_descriptor(); + if (!s) { log_fn(LOG_WARN, "No descriptor; skipping upload"); return; } @@ -249,7 +251,7 @@ void router_upload_desc_to_dirservers(void) { for(i=0;in_routers;i++) { router = rl->routers[i]; if(router->dir_port > 0) - directory_initiate_command(router, DIR_CONN_STATE_CONNECTING_UPLOAD); + directory_initiate_command(router, DIR_PURPOSE_UPLOAD_DIR, s); } }