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
This commit is contained in:
Roger Dingledine 2004-03-30 22:57:49 +00:00
parent 919a8f236e
commit 2d3ac08633
5 changed files with 218 additions and 104 deletions

View File

@ -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 ||

View File

@ -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)) {
/* queue the command on the outbuf */
directory_send_command(conn, purpose, payload);
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. */
return;
/* case 1: fall through */
}
} 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);
if(directory_send_command(conn, command) < 0) {
connection_mark_for_close(conn, 0);
}
}
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->state == DIR_CONN_STATE_SERVER_COMMAND_WAIT)
return directory_handle_command(conn);
if(conn->purpose == DIR_PURPOSE_UPLOAD_HIDSERV) {
}
assert(0); /* never reached */
}
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,38 +291,69 @@ 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.");
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,"/")) { /* 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);
conn->state = DIR_CONN_STATE_SERVER_WRITING;
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;
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.");
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:
@ -270,7 +369,16 @@ static int directory_handle_command_post(connection_t *conn,
connection_write_to_buf(answer200, strlen(answer200), conn);
break;
}
conn->state = DIR_CONN_STATE_SERVER_WRITING;
}
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.");

View File

@ -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();

View File

@ -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);

View File

@ -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;i<rl->n_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);
}
}