Commit e1d37ed6 authored by Roger Dingledine's avatar Roger Dingledine
Browse files

divorce circuit building from user connections

now we rebuild the circuit periodically (but only if it's been used),
and we can further abstract it to do incremental circuit building, etc.


svn:r233
parent 1fa0fc14
......@@ -4,6 +4,8 @@
#include "or.h"
extern or_options_t options; /* command-line and config-file options */
/********* START VARIABLES **********/
static circuit_t *global_circuitlist=NULL;
......@@ -49,12 +51,18 @@ void circuit_remove(circuit_t *circ) {
circuit_t *circuit_new(aci_t p_aci, connection_t *p_conn) {
circuit_t *circ;
struct timeval now;
if(gettimeofday(&now,NULL) < 0)
return NULL;
circ = (circuit_t *)malloc(sizeof(circuit_t));
if(!circ)
return NULL;
memset(circ,0,sizeof(circuit_t)); /* zero it out */
circ->timestamp_created = now.tv_sec;
circ->p_aci = p_aci;
circ->p_conn = p_conn;
......@@ -252,20 +260,24 @@ circuit_t *circuit_get_by_conn(connection_t *conn) {
return NULL;
}
circuit_t *circuit_get_by_edge_type(char edge_type) {
circuit_t *circ;
circuit_t *circuit_get_newest_by_edge_type(char edge_type) {
circuit_t *circ, *bestcirc=NULL;
for(circ=global_circuitlist;circ;circ = circ->next) {
if(edge_type == EDGE_AP && circ->n_conn && circ->n_conn->type == CONN_TYPE_OR) {
log(LOG_DEBUG,"circuit_get_by_edge_type(): Choosing n_aci %d.", circ->n_aci);
return circ;
if(edge_type == EDGE_AP && (!circ->p_conn || circ->p_conn->type == CONN_TYPE_AP)) {
if(!bestcirc ||
(circ->state == CIRCUIT_STATE_OPEN && bestcirc->timestamp_created < circ->timestamp_created)) {
log(LOG_DEBUG,"circuit_get_newest_by_edge_type(): Choosing n_aci %d.", circ->n_aci);
bestcirc = circ;
}
}
if(edge_type == EDGE_EXIT && circ->p_conn && circ->p_conn->type == CONN_TYPE_OR) {
return circ;
if(edge_type == EDGE_EXIT && (!circ->n_conn || circ->n_conn->type == CONN_TYPE_EXIT)) {
if(!bestcirc ||
(circ->state == CIRCUIT_STATE_OPEN && bestcirc->timestamp_created < circ->timestamp_created))
bestcirc = circ;
}
log(LOG_DEBUG,"circuit_get_by_edge_type(): Skipping p_aci %d / n_aci %d.", circ->p_aci, circ->n_aci);
}
return NULL;
return bestcirc;
}
int circuit_deliver_data_cell_from_edge(cell_t *cell, circuit_t *circ, char edge_type) {
......@@ -517,8 +529,11 @@ int circuit_consider_sending_sendme(circuit_t *circ, int edge_type) {
void circuit_close(circuit_t *circ) {
connection_t *conn;
circuit_t *youngest;
assert(circ);
if(options.APPort)
youngest = circuit_get_newest_by_edge_type(EDGE_AP);
circuit_remove(circ);
for(conn=circ->n_conn; conn; conn=conn->next_topic) {
connection_send_destroy(circ->n_aci, circ->n_conn);
......@@ -526,6 +541,11 @@ void circuit_close(circuit_t *circ) {
for(conn=circ->p_conn; conn; conn=conn->next_topic) {
connection_send_destroy(circ->p_aci, circ->p_conn);
}
if(options.APPort && youngest == circ) { /* check this after we've sent the destroys, to reduce races */
/* our current circuit just died. Launch another one pronto. */
log(LOG_INFO,"circuit_close(): Youngest circuit dying. Launching a replacement.");
circuit_launch_new(1);
}
circuit_free(circ);
}
......@@ -610,6 +630,210 @@ void circuit_dump_by_conn(connection_t *conn) {
}
}
void circuit_expire_unused_circuits(void) {
circuit_t *circ, *tmpcirc;
circuit_t *youngest;
youngest = circuit_get_newest_by_edge_type(EDGE_AP);
circ = global_circuitlist;
while(circ) {
tmpcirc = circ;
circ = circ->next;
if(tmpcirc != youngest && (!tmpcirc->p_conn || tmpcirc->p_conn->type == CONN_TYPE_AP)) {
log(LOG_DEBUG,"circuit_expire_unused_circuits(): Closing n_aci %d",tmpcirc->n_aci);
circuit_close(tmpcirc);
}
}
}
/* failure_status code: negative means reset failures to 0. Other values mean
* add that value to the current number of failures, then if we don't have too
* many failures on record, try to make a new circuit.
*/
void circuit_launch_new(int failure_status) {
static int failures=0;
if(failure_status == -1) { /* I was called because a circuit succeeded */
failures = 0;
return;
}
failures += failure_status;
retry_circuit:
if(failures > 5) {
log(LOG_INFO,"circuit_launch_new(): Giving up, %d failures.", failures);
return;
}
if(circuit_create_onion() < 0) {
failures++;
goto retry_circuit;
}
failures = 0;
return;
}
int circuit_create_onion(void) {
int i;
int routelen; /* length of the route */
unsigned int *route; /* hops in the route as an array of indexes into rarray */
unsigned char *onion; /* holds the onion */
int onionlen; /* onion length in host order */
crypt_path_t **cpath; /* defines the crypt operations that need to be performed on incoming/outgoing data */
/* choose a route */
route = (unsigned int *)router_new_route(&routelen);
if (!route) {
log(LOG_ERR,"circuit_create_onion(): Error choosing a route through the OR network.");
return -1;
}
log(LOG_DEBUG,"circuit_create_onion(): Chosen a route of length %u : ",routelen);
/* allocate memory for the crypt path */
cpath = malloc(routelen * sizeof(crypt_path_t *));
if (!cpath) {
log(LOG_ERR,"circuit_create_onion(): Error allocating memory for cpath.");
free(route);
return -1;
}
/* create an onion and calculate crypto keys */
onion = router_create_onion(route,routelen,&onionlen,cpath);
if (!onion) {
log(LOG_ERR,"circuit_create_onion(): Error creating an onion.");
free(route);
free(cpath); /* it's got nothing in it, since !onion */
return -1;
}
log(LOG_DEBUG,"circuit_create_onion(): Created an onion of size %u bytes.",onionlen);
log(LOG_DEBUG,"circuit_create_onion(): Crypt path :");
for (i=0;i<routelen;i++) {
log(LOG_DEBUG,"circuit_create_onion() : %u/%u",(cpath[i])->forwf, (cpath[i])->backf);
}
return circuit_establish_circuit(route, routelen, onion, onionlen, cpath);
}
int circuit_establish_circuit(unsigned int *route, int routelen, char *onion,
int onionlen, crypt_path_t **cpath) {
routerinfo_t *firsthop;
connection_t *n_conn;
circuit_t *circ;
/* now see if we're already connected to the first OR in 'route' */
firsthop = router_get_first_in_route(route, routelen);
assert(firsthop); /* should always be defined */
free(route); /* we don't need it anymore */
circ = circuit_new(0, NULL); /* sets circ->p_aci and circ->p_conn */
circ->state = CIRCUIT_STATE_OR_WAIT;
circ->onion = onion;
circ->onionlen = onionlen;
circ->cpath = cpath;
circ->cpathlen = routelen;
log(LOG_DEBUG,"circuit_establish_circuit(): Looking for firsthop '%s:%u'",
firsthop->address,firsthop->or_port);
n_conn = connection_twin_get_by_addr_port(firsthop->addr,firsthop->or_port);
if(!n_conn || n_conn->state != OR_CONN_STATE_OPEN) { /* not currently connected */
circ->n_addr = firsthop->addr;
circ->n_port = firsthop->or_port;
if(options.ORPort) { /* we would be connected if he were up. but he's not. */
log(LOG_DEBUG,"circuit_establish_circuit(): Route's firsthop isn't connected.");
circuit_close(circ);
return -1;
}
if(!n_conn) { /* launch the connection */
n_conn = connection_or_connect_as_op(firsthop);
if(!n_conn) { /* connect failed, forget the whole thing */
log(LOG_DEBUG,"circuit_establish_circuit(): connect to firsthop failed. Closing.");
circuit_close(circ);
return -1;
}
}
return 0; /* return success. The onion/circuit/etc will be taken care of automatically
* (may already have been) whenever n_conn reaches OR_CONN_STATE_OPEN.
*/
} else { /* it (or a twin) is already open. use it. */
circ->n_addr = n_conn->addr;
circ->n_port = n_conn->port;
return circuit_send_onion(n_conn, circ);
}
}
/* find circuits that are waiting on me, if any, and get them to send the onion */
void circuit_n_conn_open(connection_t *or_conn) {
circuit_t *circ;
log(LOG_DEBUG,"circuit_n_conn_open(): Starting.");
circ = circuit_enumerate_by_naddr_nport(NULL, or_conn->addr, or_conn->port);
for(;;) {
if(!circ)
return;
log(LOG_DEBUG,"circuit_n_conn_open(): Found circ, sending onion.");
if(circuit_send_onion(or_conn, circ) < 0) {
log(LOG_DEBUG,"circuit_n_conn_open(): circuit marked for closing.");
circuit_close(circ);
return; /* FIXME will want to try the other circuits too? */
}
circ = circuit_enumerate_by_naddr_nport(circ, or_conn->addr, or_conn->port);
}
}
int circuit_send_onion(connection_t *n_conn, circuit_t *circ) {
cell_t cell;
int tmpbuflen, dataleft;
char *tmpbuf;
circ->n_aci = get_unique_aci_by_addr_port(circ->n_addr, circ->n_port, ACI_TYPE_BOTH);
circ->n_conn = n_conn;
log(LOG_DEBUG,"circuit_send_onion(): n_conn is %s:%u",n_conn->address,n_conn->port);
/* deliver the onion as one or more create cells */
cell.command = CELL_CREATE;
cell.aci = circ->n_aci;
tmpbuflen = circ->onionlen+4;
tmpbuf = malloc(tmpbuflen);
if(!tmpbuf)
return -1;
*(uint32_t*)tmpbuf = htonl(circ->onionlen);
memcpy(tmpbuf+4, circ->onion, circ->onionlen);
dataleft = tmpbuflen;
while(dataleft) {
cell.command = CELL_CREATE;
cell.aci = circ->n_aci;
log(LOG_DEBUG,"circuit_send_onion(): Sending a create cell for the onion...");
if(dataleft >= CELL_PAYLOAD_SIZE) {
cell.length = CELL_PAYLOAD_SIZE;
memcpy(cell.payload, tmpbuf + tmpbuflen - dataleft, CELL_PAYLOAD_SIZE);
connection_write_cell_to_buf(&cell, n_conn);
dataleft -= CELL_PAYLOAD_SIZE;
} else { /* last cell */
cell.length = dataleft;
memcpy(cell.payload, tmpbuf + tmpbuflen - dataleft, dataleft);
/* fill extra space with 0 bytes */
memset(cell.payload + dataleft, 0, CELL_PAYLOAD_SIZE - dataleft);
connection_write_cell_to_buf(&cell, n_conn);
dataleft = 0;
}
}
free(tmpbuf);
circ->state = CIRCUIT_STATE_OPEN;
/* FIXME should set circ->expire to something here */
return 0;
}
/*
Local Variables:
mode:c
......
......@@ -187,6 +187,7 @@ void config_assign(or_options_t *options, struct config_line *list) {
config_compare(list, "DirFetchPeriod", CONFIG_TYPE_INT, &options->DirFetchPeriod) ||
config_compare(list, "KeepalivePeriod", CONFIG_TYPE_INT, &options->KeepalivePeriod) ||
config_compare(list, "MaxOnionsPending",CONFIG_TYPE_INT, &options->MaxOnionsPending) ||
config_compare(list, "NewCircuitPeriod",CONFIG_TYPE_INT, &options->NewCircuitPeriod) ||
config_compare(list, "Daemon", CONFIG_TYPE_BOOL, &options->Daemon) ||
config_compare(list, "TrafficShaping", CONFIG_TYPE_BOOL, &options->TrafficShaping) ||
......@@ -224,6 +225,7 @@ int getconfig(int argc, char **argv, or_options_t *options) {
options->DirFetchPeriod = 600;
options->KeepalivePeriod = 300;
options->MaxOnionsPending = 10;
options->NewCircuitPeriod = 60; /* once a minute */
// options->ReconnectPeriod = 6001;
/* get config lines from /etc/torrc and assign them */
......
......@@ -4,8 +4,6 @@
#include "or.h"
extern or_options_t options; /* command-line and config-file options */
int ap_handshake_process_socks(connection_t *conn) {
char c;
socks4_t socks4_info;
......@@ -96,211 +94,25 @@ int ap_handshake_process_socks(connection_t *conn) {
}
/* find the circuit that we should use, if there is one. */
circ = circuit_get_by_edge_type(EDGE_AP);
circ = circuit_get_newest_by_edge_type(EDGE_AP);
circ->dirty = 1;
/* now we're all ready to make an onion or send a begin */
if(circ && circ->state == CIRCUIT_STATE_OPEN) {
/* FIXME if circ not yet open, figure out how to queue this begin? */
/* add it into the linked list of topics on this circuit */
log(LOG_DEBUG,"ap_handshake_process_socks(): attaching new conn to circ. n_aci %d.", circ->n_aci);
conn->next_topic = circ->p_conn;
circ->p_conn = conn;
if(ap_handshake_send_begin(conn, circ) < 0) {
circuit_close(circ);
return -1;
}
} else {
if(ap_handshake_create_onion(conn) < 0) {
if(circ)
circuit_close(circ);
return -1;
}
}
return 0;
}
int ap_handshake_create_onion(connection_t *conn) {
int i;
int routelen = 0; /* length of the route */
unsigned int *route = NULL; /* hops in the route as an array of indexes into rarray */
unsigned char *onion = NULL; /* holds the onion */
int onionlen = 0; /* onion length in host order */
crypt_path_t **cpath = NULL; /* defines the crypt operations that need to be performed on incoming/outgoing data */
assert(conn);
/* choose a route */
route = (unsigned int *)router_new_route(&routelen);
if (!route) {
log(LOG_ERR,"ap_handshake_create_onion(): Error choosing a route through the OR network.");
if(!circ) {
log(LOG_INFO,"ap_handshake_process_socks(): No circuit ready. Closing.");
return -1;
}
log(LOG_DEBUG,"ap_handshake_create_onion(): Chosen a route of length %u : ",routelen);
#if 0
for (i=routelen-1;i>=0;i--)
{
log(LOG_DEBUG,"ap_handshake_process_ss() : %u : %s:%u, %u",routelen-i,(routerarray[route[i]])->address,ntohs((routerarray[route[i]])->port),RSA_size((routerarray[route[i]])->pkey));
}
#endif
/* allocate memory for the crypt path */
cpath = malloc(routelen * sizeof(crypt_path_t *));
if (!cpath) {
log(LOG_ERR,"ap_handshake_create_onion(): Error allocating memory for cpath.");
free(route);
return -1;
}
/* add it into the linked list of topics on this circuit */
log(LOG_DEBUG,"ap_handshake_process_socks(): attaching new conn to circ. n_aci %d.", circ->n_aci);
conn->next_topic = circ->p_conn;
circ->p_conn = conn;
/* create an onion and calculate crypto keys */
onion = router_create_onion(route,routelen,&onionlen,cpath);
if (!onion) {
log(LOG_ERR,"ap_handshake_create_onion(): Error creating an onion.");
free(route);
free(cpath); /* it's got nothing in it, since !onion */
if(ap_handshake_send_begin(conn, circ) < 0) {
circuit_close(circ);
return -1;
}
log(LOG_DEBUG,"ap_handshake_create_onion(): Created an onion of size %u bytes.",onionlen);
log(LOG_DEBUG,"ap_handshake_create_onion(): Crypt path :");
for (i=0;i<routelen;i++) {
log(LOG_DEBUG,"ap_handshake_create_onion() : %u/%u",(cpath[i])->forwf, (cpath[i])->backf);
}
return ap_handshake_establish_circuit(conn, route, routelen, onion, onionlen, cpath);
}
int ap_handshake_establish_circuit(connection_t *conn, unsigned int *route, int routelen, char *onion,
int onionlen, crypt_path_t **cpath) {
routerinfo_t *firsthop;
connection_t *n_conn;
circuit_t *circ;
/* now see if we're already connected to the first OR in 'route' */
firsthop = router_get_first_in_route(route, routelen);
assert(firsthop); /* should always be defined */
free(route); /* we don't need it anymore */
circ = circuit_new(0, conn); /* sets circ->p_aci and circ->p_conn */
circ->state = CIRCUIT_STATE_OR_WAIT;
circ->onion = onion;
circ->onionlen = onionlen;
circ->cpath = cpath;
circ->cpathlen = routelen;
log(LOG_DEBUG,"ap_handshake_establish_circuit(): Looking for firsthop '%s:%u'",
firsthop->address,firsthop->or_port);
n_conn = connection_twin_get_by_addr_port(firsthop->addr,firsthop->or_port);
if(!n_conn || n_conn->state != OR_CONN_STATE_OPEN) { /* not currently connected */
circ->n_addr = firsthop->addr;
circ->n_port = firsthop->or_port;
if(options.ORPort) { /* we would be connected if he were up. but he's not. */
log(LOG_DEBUG,"ap_handshake_establish_circuit(): Route's firsthop isn't connected.");
circuit_close(circ);
return -1;
}
conn->state = AP_CONN_STATE_OR_WAIT;
connection_stop_reading(conn); /* Stop listening for input from the AP! */
if(!n_conn) { /* launch the connection */
n_conn = connection_or_connect_as_op(firsthop);
if(!n_conn) { /* connect failed, forget the whole thing */
log(LOG_DEBUG,"ap_handshake_establish_circuit(): connect to firsthop failed. Closing.");
circuit_close(circ);
return -1;
}
}
return 0; /* return success. The onion/circuit/etc will be taken care of automatically
* (may already have been) whenever n_conn reaches OR_CONN_STATE_OPEN.
*/
} else { /* it (or a twin) is already open. use it. */
circ->n_addr = n_conn->addr;
circ->n_port = n_conn->port;
return ap_handshake_send_onion(conn, n_conn, circ);
}
}
/* find circuits that are waiting on me, if any, and get them to send the onion */
void ap_handshake_n_conn_open(connection_t *or_conn) {
circuit_t *circ;
connection_t *p_conn;
log(LOG_DEBUG,"ap_handshake_n_conn_open(): Starting.");
circ = circuit_enumerate_by_naddr_nport(NULL, or_conn->addr, or_conn->port);
for(;;) {
if(!circ)
return;
p_conn = circ->p_conn;
if(p_conn->state != AP_CONN_STATE_OR_WAIT) {
log(LOG_WARNING,"Bug: ap_handshake_n_conn_open() got an ap_conn not in OR_WAIT state.");
}
connection_start_reading(p_conn); /* resume listening for reads */
log(LOG_DEBUG,"ap_handshake_n_conn_open(): Found circ, sending onion.");
if(ap_handshake_send_onion(p_conn, or_conn, circ) < 0) {
log(LOG_DEBUG,"ap_handshake_n_conn_open(): circuit marked for closing.");
circuit_close(circ);
return; /* FIXME will want to try the other circuits too? */
}
for(p_conn = p_conn->next_topic; p_conn; p_conn = p_conn->next_topic) { /* start up any other pending topics */
if(ap_handshake_send_begin(p_conn, circ) < 0) {
circuit_close(circ);
return;
}
}
circ = circuit_enumerate_by_naddr_nport(circ, or_conn->addr, or_conn->port);
}
}
int ap_handshake_send_onion(connection_t *ap_conn, connection_t *n_conn, circuit_t *circ) {
cell_t cell;
int tmpbuflen, dataleft;
char *tmpbuf;
circ->n_aci = get_unique_aci_by_addr_port(circ->n_addr, circ->n_port, ACI_TYPE_BOTH);
circ->n_conn = n_conn;
log(LOG_DEBUG,"ap_handshake_send_onion(): n_conn is %s:%u",n_conn->address,n_conn->port);
/* deliver the onion as one or more create cells */
cell.command = CELL_CREATE;
cell.aci = circ->n_aci;
tmpbuflen = circ->onionlen+4;
tmpbuf = malloc(tmpbuflen);
if(!tmpbuf)
return -1;
*(uint32_t*)tmpbuf = htonl(circ->onionlen);
memcpy(tmpbuf+4, circ->onion, circ->onionlen);
dataleft = tmpbuflen;
while(dataleft) {
cell.command = CELL_CREATE;
cell.aci = circ->n_aci;
log(LOG_DEBUG,"ap_handshake_send_onion(): Sending a create cell for the onion...");
if(dataleft >= CELL_PAYLOAD_SIZE) {
cell.length = CELL_PAYLOAD_SIZE;
memcpy(cell.payload, tmpbuf + tmpbuflen - dataleft, CELL_PAYLOAD_SIZE);
connection_write_cell_to_buf(&cell, n_conn);
dataleft -= CELL_PAYLOAD_SIZE;
} else { /* last cell */
cell.length = dataleft;
memcpy(cell.payload, tmpbuf + tmpbuflen - dataleft, dataleft);
/* fill extra space with 0 bytes */
memset(cell.payload + dataleft, 0, CELL_PAYLOAD_SIZE - dataleft);
connection_write_cell_to_buf(&cell, n_conn);
dataleft = 0;
}
}
free(tmpbuf);
if(ap_handshake_send_begin(ap_conn, circ) < 0) {
return -1;
}
circ->state = CIRCUIT_STATE_OPEN;
/* FIXME should set circ->expire to something here */
return 0;
}
......
......@@ -334,7 +334,7 @@ int or_handshake_op_finished_sending_keys(connection_t *conn) {
conn_or_init_crypto(conn);
connection_or_set_open(conn);
ap_handshake_n_conn_open(conn); /* send the pending onions */
circuit_n_conn_open(conn); /* send the pending onion(s) */
return 0;
}
......
......@@ -302,8 +302,10 @@ int prepare_for_poll(int *timeout) {
struct timeval now; //soonest;
static long current_second = 0; /* from previous calls to gettimeofday */
static long time_to_fetch_directory = 0;
static long time_to_new_circuit = 0;
// int ms_until_conn;
cell_t cell;
circuit_t *circ;
if(gettimeofday(&now,NULL) < 0)
return -1;
......@@ -321,6 +323,17 @@ int prepare_for_poll(int *timeout) {
}
}
if(options.APPort && time_to_new_circuit < now.tv_sec) {
circuit_expire_unused_circuits();
circuit_launch_new(-1); /* tell it to forget about previous failures */
circ = circuit_get_newest_by_edge_type(EDGE_AP);
if(!circ || circ->dirty) {
log(LOG_INFO,"prepare_for_poll(): Youngest circuit missing or dirty; launching replacement.");
circuit_launch_new(0); /* make an onion and lay the circuit */
}
time_to_new_circuit = now.tv_sec + options.NewCircuitPeriod;
}
/* do housekeeping for each connection */
for(i=0;i<nfds;i++) {
tmpconn = connection_array[i];
......@@ -514,7 +527,7 @@ static void catch(int the_signal) {
}
}
void dumpstats (void) { /* dump stats to stdout */
void dumpstats(void) { /* dump stats to stdout */
int i;
connection_t *conn;
struct timeval now;
......@@ -638,7 +651,7 @@ void dump_directory_to_string(char *s, int maxlen) {
}
void daemonize() {
void daemonize(void) {
/* Fork; parent exits. */
if (fork())
exit(0);
......
......@@ -337,9 +337,9 @@ unsigned int *new_route(double cw, routerinfo_t **rarray, int rarray_len, int *r
{
int i, j;
int num_acceptable_routers;
unsigned int *route = NULL;
unsigned int *route;
unsigned int oldchoice, choice;
assert((cw >= 0) && (cw < 1) && (rarray) && (routelen) ); /* valid parameters */
*routelen = chooselen(cw);
......@@ -351,6 +351,11 @@ unsigned int *new_route(double cw, routerinfo_t **rarray, int rarray_len, int *r
num_acceptable_routers = count_acceptable_routers(rarray, rarray_len);
if(num_acceptable_routers < 2) {
log(LOG_INFO,"new_route(): Not enough acceptable routers. Failing.");