/* Copyright 2001 Matej Pfajfar, 2001-2004 Roger Dingledine. */ /* See LICENSE for licensing information */ /* $Id$ */ /** * \file relay.c * \brief Handle relay cell encryption/decryption, plus packaging and * receiving from circuits. **/ #include "or.h" extern or_options_t options; /* command-line and config-file options */ static int relay_crypt(circuit_t *circ, cell_t *cell, int cell_direction, crypt_path_t **layer_hint, char *recognized); static connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, int cell_direction); static int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, connection_t *conn, crypt_path_t *layer_hint); static void circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint); static void circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint); static int circuit_resume_edge_reading_helper(connection_t *conn, circuit_t *circ, crypt_path_t *layer_hint); static int circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint); void connection_edge_consider_sending_sendme(connection_t *conn); /** Stats: how many relay cells have originated at this hop, or have * been relayed onward (not recognized at this hop)? */ unsigned long stats_n_relay_cells_relayed = 0; /** Stats: how many relay cells have been delivered to streams at this * hop? */ unsigned long stats_n_relay_cells_delivered = 0; /** Update digest from the payload of cell. Assign integrity part to * cell. */ static void relay_set_digest(crypto_digest_env_t *digest, cell_t *cell) { char integrity[4]; relay_header_t rh; crypto_digest_add_bytes(digest, cell->payload, CELL_PAYLOAD_SIZE); crypto_digest_get_digest(digest, integrity, 4); // log_fn(LOG_DEBUG,"Putting digest of %u %u %u %u into relay cell.", // integrity[0], integrity[1], integrity[2], integrity[3]); relay_header_unpack(&rh, cell->payload); memcpy(rh.integrity, integrity, 4); relay_header_pack(cell->payload, &rh); } /** Does the digest for this circuit indicate that this cell is for us? * * Update digest from the payload of cell (with the integrity part set * to 0). If the integrity part is valid, return 1, else restore digest * and cell to their original state and return 0. */ static int relay_digest_matches(crypto_digest_env_t *digest, cell_t *cell) { char received_integrity[4], calculated_integrity[4]; relay_header_t rh; crypto_digest_env_t *backup_digest=NULL; backup_digest = crypto_digest_dup(digest); relay_header_unpack(&rh, cell->payload); memcpy(received_integrity, rh.integrity, 4); memset(rh.integrity, 0, 4); relay_header_pack(cell->payload, &rh); // log_fn(LOG_DEBUG,"Reading digest of %u %u %u %u from relay cell.", // received_integrity[0], received_integrity[1], // received_integrity[2], received_integrity[3]); crypto_digest_add_bytes(digest, cell->payload, CELL_PAYLOAD_SIZE); crypto_digest_get_digest(digest, calculated_integrity, 4); if(memcmp(received_integrity, calculated_integrity, 4)) { // log_fn(LOG_INFO,"Recognized=0 but bad digest. Not recognizing."); // (%d vs %d).", received_integrity, calculated_integrity); /* restore digest to its old form */ crypto_digest_assign(digest, backup_digest); /* restore the relay header */ memcpy(rh.integrity, received_integrity, 4); relay_header_pack(cell->payload, &rh); crypto_free_digest_env(backup_digest); return 0; } crypto_free_digest_env(backup_digest); return 1; } /** Apply cipher to CELL_PAYLOAD_SIZE bytes of in * (in place). * * If encrypt_mode is 1 then encrypt, else decrypt. * * Return -1 if the crypto fails, else return 0. */ static int relay_crypt_one_payload(crypto_cipher_env_t *cipher, char *in, int encrypt_mode) { char out[CELL_PAYLOAD_SIZE]; /* 'in' must be this size too */ relay_header_t rh; relay_header_unpack(&rh, in); // log_fn(LOG_DEBUG,"before crypt: %d",rh.recognized); if(( encrypt_mode && crypto_cipher_encrypt(cipher, in, CELL_PAYLOAD_SIZE, out)) || (!encrypt_mode && crypto_cipher_decrypt(cipher, in, CELL_PAYLOAD_SIZE, out))) { log_fn(LOG_WARN,"Error during relay encryption"); return -1; } memcpy(in,out,CELL_PAYLOAD_SIZE); relay_header_unpack(&rh, in); // log_fn(LOG_DEBUG,"after crypt: %d",rh.recognized); return 0; } /** Receive a relay cell: * - Crypt it (encrypt APward, decrypt at AP, decrypt exitward). * - Check if recognized (if exitward). * - If recognized and the digest checks out, then find if there's * a conn that the cell is intended for, and deliver it to· * connection_edge. * - Else connection_or_write_cell_to_buf to the conn on the other * side of the circuit. */ int circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, int cell_direction) { connection_t *conn=NULL; crypt_path_t *layer_hint=NULL; char recognized=0; tor_assert(cell && circ); tor_assert(cell_direction == CELL_DIRECTION_OUT || cell_direction == CELL_DIRECTION_IN); if (circ->marked_for_close) return 0; if(relay_crypt(circ, cell, cell_direction, &layer_hint, &recognized) < 0) { log_fn(LOG_WARN,"relay crypt failed. Dropping connection."); return -1; } if(recognized) { conn = relay_lookup_conn(circ, cell, cell_direction); if(cell_direction == CELL_DIRECTION_OUT) { ++stats_n_relay_cells_delivered; log_fn(LOG_DEBUG,"Sending away from origin."); if (connection_edge_process_relay_cell(cell, circ, conn, NULL) < 0) { log_fn(LOG_WARN,"connection_edge_process_relay_cell (away from origin) failed."); return -1; } } if(cell_direction == CELL_DIRECTION_IN) { ++stats_n_relay_cells_delivered; log_fn(LOG_DEBUG,"Sending to origin."); if (connection_edge_process_relay_cell(cell, circ, conn, layer_hint) < 0) { log_fn(LOG_WARN,"connection_edge_process_relay_cell (at origin) failed."); return -1; } } return 0; } /* not recognized. pass it on. */ if(cell_direction == CELL_DIRECTION_OUT) { cell->circ_id = circ->n_circ_id; /* switch it */ conn = circ->n_conn; } else { cell->circ_id = circ->p_circ_id; /* switch it */ conn = circ->p_conn; } if(!conn) { if (circ->rend_splice && cell_direction == CELL_DIRECTION_OUT) { tor_assert(circ->purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED); tor_assert(circ->rend_splice->purpose == CIRCUIT_PURPOSE_REND_ESTABLISHED); cell->circ_id = circ->rend_splice->p_circ_id; if (circuit_receive_relay_cell(cell, circ->rend_splice, CELL_DIRECTION_IN)<0) { log_fn(LOG_WARN, "Error relaying cell across rendezvous; closing circuits"); circuit_mark_for_close(circ); /* XXXX Do this here, or just return -1? */ return -1; } return 0; } log_fn(LOG_WARN,"Didn't recognize cell, but circ stops here! Closing circ."); return -1; } log_fn(LOG_DEBUG,"Passing on unrecognized cell."); ++stats_n_relay_cells_relayed; connection_or_write_cell_to_buf(cell, conn); return 0; } /** Do the appropriate en/decryptions for cell arriving on * circ in direction cell_direction. * * If cell_direction == CELL_DIRECTION_IN: * - If we're at the origin (we're the OP), for hops 1..N, * decrypt cell. If recognized, stop. * - Else (we're not the OP), encrypt one hop. Cell is not recognized. * * If cell_direction == CELL_DIRECTION_OUT: * - decrypt one hop. Check if recognized. * * If cell is recognized, set *recognized to 1, and set * *layer_hint to the hop that recognized it. * * Return -1 to indicate that we should mark the circuit for close, * else return 0. */ /* wrap this into receive_relay_cell one day */ static int relay_crypt(circuit_t *circ, cell_t *cell, int cell_direction, crypt_path_t **layer_hint, char *recognized) { crypt_path_t *thishop; relay_header_t rh; tor_assert(circ && cell && recognized); tor_assert(cell_direction == CELL_DIRECTION_IN || cell_direction == CELL_DIRECTION_OUT); if(cell_direction == CELL_DIRECTION_IN) { if(CIRCUIT_IS_ORIGIN(circ)) { /* We're at the beginning of the circuit. We'll want to do layered decrypts. */ tor_assert(circ->cpath); thishop = circ->cpath; if(thishop->state != CPATH_STATE_OPEN) { log_fn(LOG_WARN,"Relay cell before first created cell? Closing."); return -1; } do { /* Remember: cpath is in forward order, that is, first hop first. */ tor_assert(thishop); if(relay_crypt_one_payload(thishop->b_crypto, cell->payload, 0) < 0) return -1; relay_header_unpack(&rh, cell->payload); if(rh.recognized == 0) { /* it's possibly recognized. have to check digest to be sure. */ if(relay_digest_matches(thishop->b_digest, cell)) { *recognized = 1; *layer_hint = thishop; return 0; } } thishop = thishop->next; } while(thishop != circ->cpath && thishop->state == CPATH_STATE_OPEN); log_fn(LOG_WARN,"in-cell at OP not recognized. Closing."); return -1; } else { /* we're in the middle. Just one crypt. */ if(relay_crypt_one_payload(circ->p_crypto, cell->payload, 1) < 0) return -1; // log_fn(LOG_DEBUG,"Skipping recognized check, because we're not the OP."); } } else /* cell_direction == CELL_DIRECTION_OUT */ { /* we're in the middle. Just one crypt. */ if(relay_crypt_one_payload(circ->n_crypto, cell->payload, 0) < 0) return -1; relay_header_unpack(&rh, cell->payload); if (rh.recognized == 0) { /* it's possibly recognized. have to check digest to be sure. */ if(relay_digest_matches(circ->n_digest, cell)) { *recognized = 1; return 0; } } } return 0; } /** Package a relay cell: * - Encrypt it to the right layer * - connection_or_write_cell_to_buf to the right conn */ static int circuit_package_relay_cell(cell_t *cell, circuit_t *circ, int cell_direction, crypt_path_t *layer_hint) { connection_t *conn; /* where to send the cell */ crypt_path_t *thishop; /* counter for repeated crypts */ if(cell_direction == CELL_DIRECTION_OUT) { conn = circ->n_conn; if(!conn) { log_fn(LOG_WARN,"outgoing relay cell has n_conn==NULL. Dropping."); return 0; /* just drop it */ } relay_set_digest(layer_hint->f_digest, cell); thishop = layer_hint; /* moving from farthest to nearest hop */ do { tor_assert(thishop); log_fn(LOG_DEBUG,"crypting a layer of the relay cell."); if(relay_crypt_one_payload(thishop->f_crypto, cell->payload, 1) < 0) { return -1; } thishop = thishop->prev; } while (thishop != circ->cpath->prev); } else { /* incoming cell */ conn = circ->p_conn; if(!conn) { log_fn(LOG_WARN,"incoming relay cell has p_conn==NULL. Dropping."); return 0; /* just drop it */ } relay_set_digest(circ->p_digest, cell); if(relay_crypt_one_payload(circ->p_crypto, cell->payload, 1) < 0) return -1; } ++stats_n_relay_cells_relayed; connection_or_write_cell_to_buf(cell, conn); return 0; } /** If cell's stream_id matches the stream_id of any conn that's * attached to circ, return that conn, else return NULL. */ static connection_t * relay_lookup_conn(circuit_t *circ, cell_t *cell, int cell_direction) { connection_t *tmpconn; relay_header_t rh; relay_header_unpack(&rh, cell->payload); if(!rh.stream_id) return NULL; /* IN or OUT cells could have come from either direction, now * that we allow rendezvous *to* an OP. */ for(tmpconn = circ->n_streams; tmpconn; tmpconn=tmpconn->next_stream) { if(rh.stream_id == tmpconn->stream_id) { log_fn(LOG_DEBUG,"found conn for stream %d.", rh.stream_id); if(cell_direction == CELL_DIRECTION_OUT || connection_edge_is_rendezvous_stream(tmpconn)) return tmpconn; } } for(tmpconn = circ->p_streams; tmpconn; tmpconn=tmpconn->next_stream) { if(rh.stream_id == tmpconn->stream_id) { log_fn(LOG_DEBUG,"found conn for stream %d.", rh.stream_id); return tmpconn; } } for(tmpconn = circ->resolving_streams; tmpconn; tmpconn=tmpconn->next_stream) { if(rh.stream_id == tmpconn->stream_id) { log_fn(LOG_DEBUG,"found conn for stream %d.", rh.stream_id); return tmpconn; } } return NULL; /* probably a begin relay cell */ } /** Pack the relay_header_t host-order structure src into * network-order in the buffer dest. See tor-spec.txt for details * about the wire format. */ void relay_header_pack(char *dest, const relay_header_t *src) { *(uint8_t*)(dest) = src->command; set_uint16(dest+1, htons(src->recognized)); set_uint16(dest+3, htons(src->stream_id)); memcpy(dest+5, src->integrity, 4); set_uint16(dest+9, htons(src->length)); } /** Unpack the network-order buffer src into a host-order * relay_header_t structure dest. */ void relay_header_unpack(relay_header_t *dest, const char *src) { dest->command = *(uint8_t*)(src); dest->recognized = ntohs(get_uint16(src+1)); dest->stream_id = ntohs(get_uint16(src+3)); memcpy(dest->integrity, src+5, 4); dest->length = ntohs(get_uint16(src+9)); } /** Make a relay cell out of relay_command and payload, and * send it onto the open circuit circ. fromconn is the stream * that's sending the relay cell, or NULL if it's a control cell. * cpath_layer is NULL for OR->OP cells, or the destination hop * for OP->OR cells. * * If you can't send the cell, mark the circuit for close and * return -1. Else return 0. */ int connection_edge_send_command(connection_t *fromconn, circuit_t *circ, int relay_command, const char *payload, int payload_len, crypt_path_t *cpath_layer) { cell_t cell; relay_header_t rh; int cell_direction; if(!circ) { log_fn(LOG_WARN,"no circ. Closing conn."); tor_assert(fromconn); fromconn->has_sent_end = 1; /* no circ to send to */ connection_mark_for_close(fromconn); return -1; } memset(&cell, 0, sizeof(cell_t)); cell.command = CELL_RELAY; if(cpath_layer) { cell.circ_id = circ->n_circ_id; cell_direction = CELL_DIRECTION_OUT; } else { cell.circ_id = circ->p_circ_id; cell_direction = CELL_DIRECTION_IN; } memset(&rh, 0, sizeof(rh)); rh.command = relay_command; if(fromconn) rh.stream_id = fromconn->stream_id; /* else it's 0 */ rh.length = payload_len; relay_header_pack(cell.payload, &rh); if(payload_len) memcpy(cell.payload+RELAY_HEADER_SIZE, payload, payload_len); log_fn(LOG_DEBUG,"delivering %d cell %s.", relay_command, cell_direction == CELL_DIRECTION_OUT ? "forward" : "backward"); if(circuit_package_relay_cell(&cell, circ, cell_direction, cpath_layer) < 0) { log_fn(LOG_WARN,"circuit_package_relay_cell failed. Closing."); circuit_mark_for_close(circ); return -1; } return 0; } /** Translate the payload of length length, which * came from a relay 'end' cell, into a static const string describing * why the stream is closing. */ static const char * connection_edge_end_reason(char *payload, uint16_t length) { if(length < 1) { log_fn(LOG_WARN,"End cell arrived with length 0. Should be at least 1."); return "MALFORMED"; } if(*payload < _MIN_END_STREAM_REASON || *payload > _MAX_END_STREAM_REASON) { log_fn(LOG_WARN,"Reason for ending (%d) not recognized.",*payload); return "MALFORMED"; } switch(*payload) { case END_STREAM_REASON_MISC: return "misc error"; case END_STREAM_REASON_RESOLVEFAILED: return "resolve failed"; case END_STREAM_REASON_CONNECTFAILED: return "connect failed"; case END_STREAM_REASON_EXITPOLICY: return "exit policy failed"; case END_STREAM_REASON_DESTROY: return "destroyed"; case END_STREAM_REASON_DONE: return "closed normally"; case END_STREAM_REASON_TIMEOUT: return "gave up (timeout)"; } tor_assert(0); return ""; } /** How many times will I retry a stream that fails due to DNS * resolve failure? */ #define MAX_RESOLVE_FAILURES 3 /** An incoming relay cell has arrived from circuit circ to * stream conn. * * The arguments here are the same as in * connection_edge_process_relay_cell() below; this function is called * from there when conn is defined and not in an open state. */ static int connection_edge_process_relay_cell_not_open( relay_header_t *rh, cell_t *cell, circuit_t *circ, connection_t *conn, crypt_path_t *layer_hint) { uint32_t addr; int reason; routerinfo_t *exitrouter; if(rh->command == RELAY_COMMAND_END) { reason = *(cell->payload+RELAY_HEADER_SIZE); /* We have to check this here, since we aren't connected yet. */ if (rh->length >= 5 && reason == END_STREAM_REASON_EXITPOLICY) { if(conn->type != CONN_TYPE_AP) { log_fn(LOG_WARN,"Got an end because of exitpolicy, but we're not an AP. Closing."); return -1; } addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+1)); if(addr) { log_fn(LOG_INFO,"Address %s refused due to exit policy. Retrying.", conn->socks_request->address); } else { log_fn(LOG_INFO,"Address %s resolved to 0.0.0.0. Closing,", conn->socks_request->address); conn->has_sent_end = 1; /* we just got an 'end', don't need to send one */ connection_mark_for_close(conn); return 0; } client_dns_set_entry(conn->socks_request->address, addr); /* check if he *ought* to have allowed it */ exitrouter = router_get_by_digest(circ->build_state->chosen_exit_digest); if(!exitrouter) { log_fn(LOG_INFO,"Skipping broken circ (exit router vanished)"); return 0; /* this circuit is screwed and doesn't know it yet */ } if(connection_ap_can_use_exit(conn, exitrouter)) { log_fn(LOG_WARN,"Exitrouter %s seems to be more restrictive than its exit policy. Not using this router as exit for now,", exitrouter->nickname); exit_policy_free(exitrouter->exit_policy); exitrouter->exit_policy = router_parse_exit_policy_from_string("reject *:*"); } conn->state = AP_CONN_STATE_CIRCUIT_WAIT; circuit_detach_stream(circ,conn); if(connection_ap_handshake_attach_circuit(conn) >= 0) return 0; log_fn(LOG_INFO,"Giving up on retrying (from exitpolicy); conn can't be handled."); /* else, conn will get closed below */ } else if (rh->length && reason == END_STREAM_REASON_RESOLVEFAILED) { if (client_dns_incr_failures(conn->socks_request->address) < MAX_RESOLVE_FAILURES) { /* We haven't retried too many times; reattach the connection. */ log_fn(LOG_INFO,"Resolve of '%s' failed, trying again.", conn->socks_request->address); circuit_log_path(LOG_INFO,circ); conn->state = AP_CONN_STATE_CIRCUIT_WAIT; circuit_detach_stream(circ,conn); tor_assert(circ->timestamp_dirty); circ->timestamp_dirty -= options.NewCircuitPeriod; /* make sure not to expire/retry the stream quite yet */ conn->timestamp_lastread = time(NULL); if(connection_ap_handshake_attach_circuit(conn) >= 0) return 0; /* else, conn will get closed below */ log_fn(LOG_INFO,"Giving up on retrying (from resolvefailed); conn can't be handled."); } else { log_fn(LOG_WARN,"Have tried resolving address %s at %d different places. Giving up.", conn->socks_request->address, MAX_RESOLVE_FAILURES); } } log_fn(LOG_INFO,"Edge got end (%s) before we're connected. Marking for close.", connection_edge_end_reason(cell->payload+RELAY_HEADER_SIZE, rh->length)); if(CIRCUIT_IS_ORIGIN(circ)) circuit_log_path(LOG_INFO,circ); conn->has_sent_end = 1; /* we just got an 'end', don't need to send one */ connection_mark_for_close(conn); return 0; } if(conn->type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_CONNECTED) { if(conn->state != AP_CONN_STATE_CONNECT_WAIT) { log_fn(LOG_WARN,"Got 'connected' while not in state connect_wait. Dropping."); return 0; } // log_fn(LOG_INFO,"Connected! Notifying application."); conn->state = AP_CONN_STATE_OPEN; log_fn(LOG_INFO,"'connected' received after %d seconds.", (int)(time(NULL) - conn->timestamp_lastread)); if (rh->length >= 4) { addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE)); if(!addr) { log_fn(LOG_INFO,"...but it claims the IP address was 0.0.0.0. Closing."); connection_edge_end(conn, END_STREAM_REASON_MISC, conn->cpath_layer); connection_mark_for_close(conn); return 0; } client_dns_set_entry(conn->socks_request->address, addr); } circuit_log_path(LOG_INFO,circ); connection_ap_handshake_socks_reply(conn, NULL, 0, 1); conn->socks_request->has_finished = 1; /* handle anything that might have queued */ if (connection_edge_package_raw_inbuf(conn) < 0) { connection_edge_end(conn, END_STREAM_REASON_MISC, conn->cpath_layer); connection_mark_for_close(conn); return 0; } return 0; } if(conn->type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_RESOLVED) { if (conn->state != AP_CONN_STATE_RESOLVE_WAIT) { log_fn(LOG_WARN,"Got a 'resolved' cell while not in state resolve_wait. Dropping."); return 0; } tor_assert(conn->socks_request->command == SOCKS_COMMAND_RESOLVE); if (rh->length < 2 || cell->payload[RELAY_HEADER_SIZE+1]+2>rh->length) { log_fn(LOG_WARN, "Dropping malformed 'resolved' cell"); conn->has_sent_end = 1; connection_mark_for_close(conn); return 0; } connection_ap_handshake_socks_resolved(conn, cell->payload[RELAY_HEADER_SIZE], /*answer_type*/ cell->payload[RELAY_HEADER_SIZE+1], /*answer_len*/ cell->payload+RELAY_HEADER_SIZE+2); /* answer */ conn->socks_request->has_finished = 1; conn->has_sent_end = 1; connection_mark_for_close(conn); conn->hold_open_until_flushed = 1; return 0; } log_fn(LOG_WARN,"Got an unexpected relay command %d, in state %d (%s). Closing.", rh->command, conn->state, conn_state_to_string[conn->type][conn->state]); connection_edge_end(conn, END_STREAM_REASON_MISC, conn->cpath_layer); connection_mark_for_close(conn); return -1; } /** An incoming relay cell has arrived on circuit circ. If * conn is NULL this is a control cell, else cell is * destined for conn. * * If layer_hint is defined, then we're the origin of the * circuit, and it specifies the hop that packaged cell. * * Return -1 if you want to tear down the circuit, else 0. */ static int connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, connection_t *conn, crypt_path_t *layer_hint) { static int num_seen=0; relay_header_t rh; tor_assert(cell && circ); relay_header_unpack(&rh, cell->payload); // log_fn(LOG_DEBUG,"command %d stream %d", rh.command, rh.stream_id); num_seen++; log_fn(LOG_DEBUG,"Now seen %d relay cells here.", num_seen); /* either conn is NULL, in which case we've got a control cell, or else * conn points to the recognized stream. */ if(conn && conn->state != AP_CONN_STATE_OPEN && conn->state != EXIT_CONN_STATE_OPEN) { return connection_edge_process_relay_cell_not_open( &rh, cell, circ, conn, layer_hint); } switch(rh.command) { case RELAY_COMMAND_DROP: log_fn(LOG_INFO,"Got a relay-level padding cell. Dropping."); return 0; case RELAY_COMMAND_BEGIN: if (layer_hint && circ->purpose != CIRCUIT_PURPOSE_S_REND_JOINED) { log_fn(LOG_WARN,"relay begin request unsupported at AP. Dropping."); return 0; } if(conn) { log_fn(LOG_WARN,"begin cell for known stream. Dropping."); return 0; } connection_exit_begin_conn(cell, circ); return 0; case RELAY_COMMAND_DATA: ++stats_n_data_cells_received; if((layer_hint && --layer_hint->deliver_window < 0) || (!layer_hint && --circ->deliver_window < 0)) { log_fn(LOG_WARN,"(relay data) circ deliver_window below 0. Killing."); connection_edge_end(conn, END_STREAM_REASON_MISC, conn->cpath_layer); connection_mark_for_close(conn); return -1; } log_fn(LOG_DEBUG,"circ deliver_window now %d.", layer_hint ? layer_hint->deliver_window : circ->deliver_window); circuit_consider_sending_sendme(circ, layer_hint); if(!conn) { log_fn(LOG_INFO,"data cell dropped, unknown stream."); return 0; } if(--conn->deliver_window < 0) { /* is it below 0 after decrement? */ log_fn(LOG_WARN,"(relay data) conn deliver_window below 0. Killing."); return -1; /* somebody's breaking protocol. kill the whole circuit. */ } stats_n_data_bytes_received += rh.length; connection_write_to_buf(cell->payload + RELAY_HEADER_SIZE, rh.length, conn); connection_edge_consider_sending_sendme(conn); return 0; case RELAY_COMMAND_END: if(!conn) { log_fn(LOG_INFO,"end cell (%s) dropped, unknown stream.", connection_edge_end_reason(cell->payload+RELAY_HEADER_SIZE, rh.length)); return 0; } /* XXX add to this log_fn the exit node's nickname? */ log_fn(LOG_INFO,"end cell (%s) for stream %d. Removing stream.", connection_edge_end_reason(cell->payload+RELAY_HEADER_SIZE, rh.length), conn->stream_id); #ifdef HALF_OPEN conn->done_sending = 1; shutdown(conn->s, 1); /* XXX check return; refactor NM */ if (conn->done_receiving) { /* We just *got* an end; no reason to send one. */ conn->has_sent_end = 1; connection_mark_for_close(conn); conn->hold_open_until_flushed = 1; } #else /* We just *got* an end; no reason to send one. */ conn->has_sent_end = 1; if(!conn->marked_for_close) { /* only mark it if not already marked. it's possible to * get the 'end' right around when the client hangs up on us. */ connection_mark_for_close(conn); } conn->hold_open_until_flushed = 1; #endif return 0; case RELAY_COMMAND_EXTEND: if(conn) { log_fn(LOG_WARN,"'extend' for non-zero stream. Dropping."); return 0; } return circuit_extend(cell, circ); case RELAY_COMMAND_EXTENDED: if(!layer_hint) { log_fn(LOG_WARN,"'extended' unsupported at non-origin. Dropping."); return 0; } log_fn(LOG_DEBUG,"Got an extended cell! Yay."); if(circuit_finish_handshake(circ, cell->payload+RELAY_HEADER_SIZE) < 0) { log_fn(LOG_WARN,"circuit_finish_handshake failed."); return -1; } if (circuit_send_next_onion_skin(circ)<0) { log_fn(LOG_INFO,"circuit_send_next_onion_skin() failed."); return -1; } return 0; case RELAY_COMMAND_TRUNCATE: if(layer_hint) { log_fn(LOG_WARN,"'truncate' unsupported at origin. Dropping."); return 0; } if(circ->n_conn) { connection_send_destroy(circ->n_circ_id, circ->n_conn); circ->n_conn = NULL; } log_fn(LOG_DEBUG, "Processed 'truncate', replying."); connection_edge_send_command(NULL, circ, RELAY_COMMAND_TRUNCATED, NULL, 0, NULL); return 0; case RELAY_COMMAND_TRUNCATED: if(!layer_hint) { log_fn(LOG_WARN,"'truncated' unsupported at non-origin. Dropping."); return 0; } circuit_truncated(circ, layer_hint); return 0; case RELAY_COMMAND_CONNECTED: if(conn) { log_fn(LOG_WARN,"'connected' unsupported while open. Closing circ."); return -1; } log_fn(LOG_INFO,"'connected' received, no conn attached anymore. Ignoring."); return 0; case RELAY_COMMAND_SENDME: if(!conn) { if(layer_hint) { layer_hint->package_window += CIRCWINDOW_INCREMENT; log_fn(LOG_DEBUG,"circ-level sendme at origin, packagewindow %d.", layer_hint->package_window); circuit_resume_edge_reading(circ, layer_hint); } else { circ->package_window += CIRCWINDOW_INCREMENT; log_fn(LOG_DEBUG,"circ-level sendme at non-origin, packagewindow %d.", circ->package_window); circuit_resume_edge_reading(circ, layer_hint); } return 0; } conn->package_window += STREAMWINDOW_INCREMENT; log_fn(LOG_DEBUG,"stream-level sendme, packagewindow now %d.", conn->package_window); connection_start_reading(conn); connection_edge_package_raw_inbuf(conn); /* handle whatever might still be on the inbuf */ return 0; case RELAY_COMMAND_RESOLVE: if (layer_hint) { log_fn(LOG_WARN,"resolve request unsupported at AP; dropping."); return 0; } else if (conn) { log_fn(LOG_WARN, "resolve request for known stream; dropping."); return 0; } else if (circ->purpose != CIRCUIT_PURPOSE_OR) { log_fn(LOG_WARN, "resolve request on circ with purpose %d; dropping", circ->purpose); return 0; } connection_exit_begin_resolve(cell, circ); return 0; case RELAY_COMMAND_RESOLVED: if(conn) { log_fn(LOG_WARN,"'resolved' unsupported while open. Closing circ."); return -1; } log_fn(LOG_INFO,"'resolved' received, no conn attached anymore. Ignoring."); return 0; case RELAY_COMMAND_ESTABLISH_INTRO: case RELAY_COMMAND_ESTABLISH_RENDEZVOUS: case RELAY_COMMAND_INTRODUCE1: case RELAY_COMMAND_INTRODUCE2: case RELAY_COMMAND_INTRODUCE_ACK: case RELAY_COMMAND_RENDEZVOUS1: case RELAY_COMMAND_RENDEZVOUS2: case RELAY_COMMAND_INTRO_ESTABLISHED: case RELAY_COMMAND_RENDEZVOUS_ESTABLISHED: rend_process_relay_cell(circ, rh.command, rh.length, cell->payload+RELAY_HEADER_SIZE); return 0; } log_fn(LOG_WARN,"unknown relay command %d.",rh.command); return -1; } uint64_t stats_n_data_cells_packaged = 0; uint64_t stats_n_data_bytes_packaged = 0; uint64_t stats_n_data_cells_received = 0; uint64_t stats_n_data_bytes_received = 0; /** While conn->inbuf has an entire relay payload of bytes on it, * and the appropriate package windows aren't empty, grab a cell * and send it down the circuit. * * Return -1 if conn should be marked for close, else return 0. */ int connection_edge_package_raw_inbuf(connection_t *conn) { int amount_to_process, length; char payload[CELL_PAYLOAD_SIZE]; circuit_t *circ; tor_assert(conn); tor_assert(!connection_speaks_cells(conn)); repeat_connection_edge_package_raw_inbuf: circ = circuit_get_by_conn(conn); if(!circ) { log_fn(LOG_INFO,"conn has no circuits! Closing."); return -1; } if(circuit_consider_stop_edge_reading(circ, conn->cpath_layer)) return 0; if(conn->package_window <= 0) { log_fn(LOG_WARN,"called with package_window %d. Tell Roger.", conn->package_window); connection_stop_reading(conn); return 0; } amount_to_process = buf_datalen(conn->inbuf); if(!amount_to_process) return 0; if(amount_to_process > RELAY_PAYLOAD_SIZE) { length = RELAY_PAYLOAD_SIZE; } else { length = amount_to_process; } stats_n_data_bytes_packaged += length; stats_n_data_cells_packaged += 1; connection_fetch_from_buf(payload, length, conn); log_fn(LOG_DEBUG,"(%d) Packaging %d bytes (%d waiting).", conn->s, length, (int)buf_datalen(conn->inbuf)); if(connection_edge_send_command(conn, circ, RELAY_COMMAND_DATA, payload, length, conn->cpath_layer) < 0) return 0; /* circuit is closed, don't continue */ if(!conn->cpath_layer) { /* non-rendezvous exit */ tor_assert(circ->package_window > 0); circ->package_window--; } else { /* we're an AP, or an exit on a rendezvous circ */ tor_assert(conn->cpath_layer->package_window > 0); conn->cpath_layer->package_window--; } if(--conn->package_window <= 0) { /* is it 0 after decrement? */ connection_stop_reading(conn); log_fn(LOG_DEBUG,"conn->package_window reached 0."); circuit_consider_stop_edge_reading(circ, conn->cpath_layer); return 0; /* don't process the inbuf any more */ } log_fn(LOG_DEBUG,"conn->package_window is now %d",conn->package_window); /* handle more if there's more, or return 0 if there isn't */ goto repeat_connection_edge_package_raw_inbuf; } /** Called when we've just received a relay data cell, or when * we've just finished flushing all bytes to stream conn. * * If conn->outbuf is not too full, and our deliver window is * low, send back a suitable number of stream-level sendme cells. */ void connection_edge_consider_sending_sendme(connection_t *conn) { circuit_t *circ; if(connection_outbuf_too_full(conn)) return; circ = circuit_get_by_conn(conn); if(!circ) { /* this can legitimately happen if the destroy has already * arrived and torn down the circuit */ log_fn(LOG_INFO,"No circuit associated with conn. Skipping."); return; } while(conn->deliver_window < STREAMWINDOW_START - STREAMWINDOW_INCREMENT) { log_fn(LOG_DEBUG,"Outbuf %d, Queueing stream sendme.", conn->outbuf_flushlen); conn->deliver_window += STREAMWINDOW_INCREMENT; if(connection_edge_send_command(conn, circ, RELAY_COMMAND_SENDME, NULL, 0, conn->cpath_layer) < 0) { log_fn(LOG_WARN,"connection_edge_send_command failed. Returning."); return; /* the circuit's closed, don't continue */ } } } /** The circuit circ has received a circuit-level sendme * (on hop layer_hint, if we're the OP). Go through all the * attached streams and let them resume reading and packaging, if * their stream windows allow it. */ static void circuit_resume_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) { log_fn(LOG_DEBUG,"resuming"); /* have to check both n_streams and p_streams, to handle rendezvous */ if(circuit_resume_edge_reading_helper(circ->n_streams, circ, layer_hint) >= 0) circuit_resume_edge_reading_helper(circ->p_streams, circ, layer_hint); } /** A helper function for circuit_resume_edge_reading() above. * The arguments are the same, except that conn is the head * of a linked list of edge streams that should each be considered. */ static int circuit_resume_edge_reading_helper(connection_t *conn, circuit_t *circ, crypt_path_t *layer_hint) { for( ; conn; conn=conn->next_stream) { if((!layer_hint && conn->package_window > 0) || (layer_hint && conn->package_window > 0 && conn->cpath_layer == layer_hint)) { connection_start_reading(conn); /* handle whatever might still be on the inbuf */ connection_edge_package_raw_inbuf(conn); /* If the circuit won't accept any more data, return without looking * at any more of the streams. Any connections that should be stopped * have already been stopped by connection_edge_package_raw_inbuf. */ if(circuit_consider_stop_edge_reading(circ, layer_hint)) return -1; } } return 0; } /** Check if the package window for circ is empty (at * hop layer_hint if it's defined). * * If yes, tell edge streams to stop reading and return -1. * Else return 0. */ static int circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) { connection_t *conn = NULL; log_fn(LOG_DEBUG,"considering"); if(!layer_hint && circ->package_window <= 0) { log_fn(LOG_DEBUG,"yes, not-at-origin. stopped."); for(conn = circ->n_streams; conn; conn=conn->next_stream) connection_stop_reading(conn); return -1; } else if(layer_hint && layer_hint->package_window <= 0) { log_fn(LOG_DEBUG,"yes, at-origin. stopped."); for(conn = circ->n_streams; conn; conn=conn->next_stream) if(conn->cpath_layer == layer_hint) connection_stop_reading(conn); for(conn = circ->p_streams; conn; conn=conn->next_stream) if(conn->cpath_layer == layer_hint) connection_stop_reading(conn); return -1; } return 0; } /** Check if the deliver_window for circuit circ (at hop * layer_hint if it's defined) is low enough that we should * send a circuit-level sendme back down the circuit. If so, send * enough sendmes that the window would be overfull if we sent any * more. */ static void circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) { // log_fn(LOG_INFO,"Considering: layer_hint is %s", // layer_hint ? "defined" : "null"); while((layer_hint ? layer_hint->deliver_window : circ->deliver_window) < CIRCWINDOW_START - CIRCWINDOW_INCREMENT) { log_fn(LOG_DEBUG,"Queueing circuit sendme."); if(layer_hint) layer_hint->deliver_window += CIRCWINDOW_INCREMENT; else circ->deliver_window += CIRCWINDOW_INCREMENT; if(connection_edge_send_command(NULL, circ, RELAY_COMMAND_SENDME, NULL, 0, layer_hint) < 0) { log_fn(LOG_WARN,"connection_edge_send_command failed. Circuit's closed."); return; /* the circuit's closed, don't continue */ } } }