diff options
Diffstat (limited to 'src/or/relay.c')
-rw-r--r-- | src/or/relay.c | 407 |
1 files changed, 292 insertions, 115 deletions
diff --git a/src/or/relay.c b/src/or/relay.c index b637fadf59..fda9e89ca4 100644 --- a/src/or/relay.c +++ b/src/or/relay.c @@ -1,7 +1,7 @@ /* Copyright (c) 2001 Matej Pfajfar. * Copyright (c) 2001-2004, Roger Dingledine. * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson. - * Copyright (c) 2007-2011, The Tor Project, Inc. */ + * Copyright (c) 2007-2012, The Tor Project, Inc. */ /* See LICENSE for licensing information */ /** @@ -11,6 +11,7 @@ **/ #include <math.h> +#define RELAY_PRIVATE #include "or.h" #include "buffers.h" #include "circuitbuild.h" @@ -24,6 +25,7 @@ #include "main.h" #include "mempool.h" #include "networkstatus.h" +#include "nodelist.h" #include "policies.h" #include "reasons.h" #include "relay.h" @@ -32,9 +34,6 @@ #include "routerlist.h" #include "routerparse.h" -static int relay_crypt(circuit_t *circ, cell_t *cell, - cell_direction_t cell_direction, - crypt_path_t **layer_hint, char *recognized); static edge_connection_t *relay_lookup_conn(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t *layer_hint); @@ -53,11 +52,6 @@ static int circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint); static int circuit_queue_streams_are_blocked(circuit_t *circ); -/* XXXX023 move this all to compat_libevent */ -/** Cache the current hi-res time; the cache gets reset when libevent - * calls us. */ -static struct timeval cached_time_hires = {0, 0}; - /** Stop reading on edge connections when we have this many cells * waiting on the appropriate queue. */ #define CELL_QUEUE_HIGHWATER_SIZE 256 @@ -65,21 +59,6 @@ static struct timeval cached_time_hires = {0, 0}; * cells. */ #define CELL_QUEUE_LOWWATER_SIZE 64 -static void -tor_gettimeofday_cached(struct timeval *tv) -{ - if (cached_time_hires.tv_sec == 0) { - tor_gettimeofday(&cached_time_hires); - } - *tv = cached_time_hires; -} - -void -tor_gettimeofday_cache_clear(void) -{ - cached_time_hires.tv_sec = 0; -} - /** Stats: how many relay cells have originated at this hop, or have * been relayed onward (not recognized at this hop)? */ @@ -93,7 +72,7 @@ uint64_t stats_n_relay_cells_delivered = 0; * cell. */ static void -relay_set_digest(crypto_digest_env_t *digest, cell_t *cell) +relay_set_digest(crypto_digest_t *digest, cell_t *cell) { char integrity[4]; relay_header_t rh; @@ -114,11 +93,11 @@ relay_set_digest(crypto_digest_env_t *digest, cell_t *cell) * and cell to their original state and return 0. */ static int -relay_digest_matches(crypto_digest_env_t *digest, cell_t *cell) +relay_digest_matches(crypto_digest_t *digest, cell_t *cell) { char received_integrity[4], calculated_integrity[4]; relay_header_t rh; - crypto_digest_env_t *backup_digest=NULL; + crypto_digest_t *backup_digest=NULL; backup_digest = crypto_digest_dup(digest); @@ -142,10 +121,10 @@ relay_digest_matches(crypto_digest_env_t *digest, cell_t *cell) /* restore the relay header */ memcpy(rh.integrity, received_integrity, 4); relay_header_pack(cell->payload, &rh); - crypto_free_digest_env(backup_digest); + crypto_digest_free(backup_digest); return 0; } - crypto_free_digest_env(backup_digest); + crypto_digest_free(backup_digest); return 1; } @@ -157,7 +136,7 @@ relay_digest_matches(crypto_digest_env_t *digest, cell_t *cell) * Return -1 if the crypto fails, else return 0. */ static int -relay_crypt_one_payload(crypto_cipher_env_t *cipher, uint8_t *in, +relay_crypt_one_payload(crypto_cipher_t *cipher, uint8_t *in, int encrypt_mode) { int r; @@ -296,7 +275,7 @@ circuit_receive_relay_cell(cell_t *cell, circuit_t *circ, * Return -1 to indicate that we should mark the circuit for close, * else return 0. */ -static int +int relay_crypt(circuit_t *circ, cell_t *cell, cell_direction_t cell_direction, crypt_path_t **layer_hint, char *recognized) { @@ -608,7 +587,7 @@ relay_send_command_from_edge(streamid_t stream_id, circuit_t *circ, /* If no RELAY_EARLY cells can be sent over this circuit, log which * commands have been sent as RELAY_EARLY cells before; helps debug * task 878. */ - smartlist_t *commands_list = smartlist_create(); + smartlist_t *commands_list = smartlist_new(); int i = 0; char *commands = NULL; for (; i < origin_circ->relay_early_cells_sent; i++) @@ -648,6 +627,7 @@ connection_edge_send_command(edge_connection_t *fromconn, { /* XXXX NM Split this function into a separate versions per circuit type? */ circuit_t *circ; + crypt_path_t *cpath_layer = fromconn->cpath_layer; tor_assert(fromconn); circ = fromconn->on_circuit; @@ -662,7 +642,8 @@ connection_edge_send_command(edge_connection_t *fromconn, if (!circ) { if (fromconn->_base.type == CONN_TYPE_AP) { log_info(LD_APP,"no circ. Closing conn."); - connection_mark_unattached_ap(fromconn, END_STREAM_REASON_INTERNAL); + connection_mark_unattached_ap(EDGE_TO_ENTRY_CONN(fromconn), + END_STREAM_REASON_INTERNAL); } else { log_info(LD_EXIT,"no circ. Closing conn."); fromconn->edge_has_sent_end = 1; /* no circ to send to */ @@ -674,7 +655,7 @@ connection_edge_send_command(edge_connection_t *fromconn, return relay_send_command_from_edge(fromconn->stream_id, circ, relay_command, payload, - payload_len, fromconn->cpath_layer); + payload_len, cpath_layer); } /** How many times will I retry a stream that fails due to DNS @@ -702,22 +683,24 @@ edge_reason_is_retriable(int reason) static int connection_ap_process_end_not_open( relay_header_t *rh, cell_t *cell, origin_circuit_t *circ, - edge_connection_t *conn, crypt_path_t *layer_hint) + entry_connection_t *conn, crypt_path_t *layer_hint) { struct in_addr in; - routerinfo_t *exitrouter; + node_t *exitrouter; int reason = *(cell->payload+RELAY_HEADER_SIZE); int control_reason = reason | END_STREAM_REASON_FLAG_REMOTE; + edge_connection_t *edge_conn = ENTRY_TO_EDGE_CONN(conn); (void) layer_hint; /* unused */ if (rh->length > 0 && edge_reason_is_retriable(reason) && - !connection_edge_is_rendezvous_stream(conn) /* avoid retry if rend */ - ) { + /* avoid retry if rend */ + !connection_edge_is_rendezvous_stream(edge_conn)) { + const char *chosen_exit_digest = + circ->build_state->chosen_exit->identity_digest; log_info(LD_APP,"Address '%s' refused due to '%s'. Considering retrying.", safe_str(conn->socks_request->address), stream_end_reason_to_string(reason)); - exitrouter = - router_get_by_digest(circ->build_state->chosen_exit->identity_digest); + exitrouter = node_get_mutable_by_id(chosen_exit_digest); switch (reason) { case END_STREAM_REASON_EXITPOLICY: if (rh->length >= 5) { @@ -752,13 +735,13 @@ connection_ap_process_end_not_open( log_info(LD_APP, "Exitrouter %s seems to be more restrictive than its exit " "policy. Not using this router as exit for now.", - router_describe(exitrouter)); - policies_set_router_exitpolicy_to_reject_all(exitrouter); + node_describe(exitrouter)); + policies_set_node_exitpolicy_to_reject_all(exitrouter); } /* rewrite it to an IP if we learned one. */ if (addressmap_rewrite(conn->socks_request->address, sizeof(conn->socks_request->address), - NULL)) { + NULL, NULL)) { control_event_stream_status(conn, STREAM_EVENT_REMAP, 0); } if (conn->chosen_exit_optional || @@ -793,7 +776,7 @@ connection_ap_process_end_not_open( /* We haven't retried too many times; reattach the connection. */ circuit_log_path(LOG_INFO,LD_APP,circ); /* Mark this circuit "unusable for new streams". */ - /* XXXX023 this is a kludgy way to do this. */ + /* XXXX024 this is a kludgy way to do this. */ tor_assert(circ->_base.timestamp_dirty); circ->_base.timestamp_dirty -= get_options()->MaxCircuitDirtiness; @@ -818,7 +801,7 @@ connection_ap_process_end_not_open( case END_STREAM_REASON_HIBERNATING: case END_STREAM_REASON_RESOURCELIMIT: if (exitrouter) { - policies_set_router_exitpolicy_to_reject_all(exitrouter); + policies_set_node_exitpolicy_to_reject_all(exitrouter); } if (conn->chosen_exit_optional) { /* stop wanting a specific exit */ @@ -838,7 +821,7 @@ connection_ap_process_end_not_open( stream_end_reason_to_string(rh->length > 0 ? reason : -1)); circuit_log_path(LOG_INFO,LD_APP,circ); /* need to test because of detach_retriable */ - if (!conn->_base.marked_for_close) + if (!ENTRY_TO_CONN(conn)->marked_for_close) connection_mark_unattached_ap(conn, control_reason); return 0; } @@ -847,7 +830,7 @@ connection_ap_process_end_not_open( * dotted-quad representation of <b>new_addr</b> (given in host order), * and send an appropriate REMAP event. */ static void -remap_event_helper(edge_connection_t *conn, uint32_t new_addr) +remap_event_helper(entry_connection_t *conn, uint32_t new_addr) { struct in_addr in; @@ -873,7 +856,8 @@ connection_edge_process_relay_cell_not_open( if (rh->command == RELAY_COMMAND_END) { if (CIRCUIT_IS_ORIGIN(circ) && conn->_base.type == CONN_TYPE_AP) { return connection_ap_process_end_not_open(rh, cell, - TO_ORIGIN_CIRCUIT(circ), conn, + TO_ORIGIN_CIRCUIT(circ), + EDGE_TO_ENTRY_CONN(conn), layer_hint); } else { /* we just got an 'end', don't need to send one */ @@ -887,6 +871,7 @@ connection_edge_process_relay_cell_not_open( if (conn->_base.type == CONN_TYPE_AP && rh->command == RELAY_COMMAND_CONNECTED) { + entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); tor_assert(CIRCUIT_IS_ORIGIN(circ)); if (conn->_base.state != AP_CONN_STATE_CONNECT_WAIT) { log_fn(LOG_PROTOCOL_WARN, LD_APP, @@ -901,29 +886,27 @@ connection_edge_process_relay_cell_not_open( int ttl; if (!addr || (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0))) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP, - "...but it claims the IP address was %s. Closing.", buf); + log_info(LD_APP, "...but it claims the IP address was %s. Closing.", + fmt_addr32(addr)); connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_TORPROTOCOL); return 0; } if (rh->length >= 8) ttl = (int)ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+4)); else ttl = -1; - client_dns_set_addressmap(conn->socks_request->address, addr, - conn->chosen_exit_name, ttl); + client_dns_set_addressmap(entry_conn->socks_request->address, addr, + entry_conn->chosen_exit_name, ttl); - remap_event_helper(conn, addr); + remap_event_helper(entry_conn, addr); } circuit_log_path(LOG_INFO,LD_APP,TO_ORIGIN_CIRCUIT(circ)); /* don't send a socks reply to transparent conns */ - if (!conn->socks_request->has_finished) - connection_ap_handshake_socks_reply(conn, NULL, 0, 0); + tor_assert(entry_conn->socks_request != NULL); + if (!entry_conn->socks_request->has_finished) + connection_ap_handshake_socks_reply(entry_conn, NULL, 0, 0); /* Was it a linked dir conn? If so, a dir request just started to * fetch something; this could be a bootstrap status milestone. */ @@ -946,6 +929,12 @@ connection_edge_process_relay_cell_not_open( break; } } + /* This is definitely a success, so forget about any pending data we + * had sent. */ + if (entry_conn->pending_optimistic_data) { + generic_buffer_free(entry_conn->pending_optimistic_data); + entry_conn->pending_optimistic_data = NULL; + } /* handle anything that might have queued */ if (connection_edge_package_raw_inbuf(conn, 1, NULL) < 0) { @@ -960,17 +949,18 @@ connection_edge_process_relay_cell_not_open( int ttl; int answer_len; uint8_t answer_type; + entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); if (conn->_base.state != AP_CONN_STATE_RESOLVE_WAIT) { log_fn(LOG_PROTOCOL_WARN, LD_APP, "Got a 'resolved' cell while " "not in state resolve_wait. Dropping."); return 0; } - tor_assert(SOCKS_COMMAND_IS_RESOLVE(conn->socks_request->command)); + tor_assert(SOCKS_COMMAND_IS_RESOLVE(entry_conn->socks_request->command)); answer_len = cell->payload[RELAY_HEADER_SIZE+1]; if (rh->length < 2 || answer_len+2>rh->length) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Dropping malformed 'resolved' cell"); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_TORPROTOCOL); return 0; } answer_type = cell->payload[RELAY_HEADER_SIZE]; @@ -983,19 +973,17 @@ connection_edge_process_relay_cell_not_open( uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); if (get_options()->ClientDNSRejectInternalAddresses && is_internal_IP(addr, 0)) { - char buf[INET_NTOA_BUF_LEN]; - struct in_addr a; - a.s_addr = htonl(addr); - tor_inet_ntoa(&a, buf, sizeof(buf)); - log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", buf); - connection_ap_handshake_socks_resolved(conn, + log_info(LD_APP,"Got a resolve with answer %s. Rejecting.", + fmt_addr32(addr)); + connection_ap_handshake_socks_resolved(entry_conn, RESOLVED_TYPE_ERROR_TRANSIENT, 0, NULL, 0, TIME_MAX); - connection_mark_unattached_ap(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_unattached_ap(entry_conn, + END_STREAM_REASON_TORPROTOCOL); return 0; } } - connection_ap_handshake_socks_resolved(conn, + connection_ap_handshake_socks_resolved(entry_conn, answer_type, cell->payload[RELAY_HEADER_SIZE+1], /*answer_len*/ cell->payload+RELAY_HEADER_SIZE+2, /*answer*/ @@ -1003,9 +991,9 @@ connection_edge_process_relay_cell_not_open( -1); if (answer_type == RESOLVED_TYPE_IPV4 && answer_len == 4) { uint32_t addr = ntohl(get_uint32(cell->payload+RELAY_HEADER_SIZE+2)); - remap_event_helper(conn, addr); + remap_event_helper(entry_conn, addr); } - connection_mark_unattached_ap(conn, + connection_mark_unattached_ap(entry_conn, END_STREAM_REASON_DONE | END_STREAM_REASON_FLAG_ALREADY_SOCKS_REPLIED); return 0; @@ -1039,6 +1027,9 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, relay_header_t rh; unsigned domain = layer_hint?LD_APP:LD_EXIT; int reason; + int optimistic_data = 0; /* Set to 1 if we receive data on a stream + * that's in the EXIT_CONN_STATE_RESOLVING + * or EXIT_CONN_STATE_CONNECTING states. */ tor_assert(cell); tor_assert(circ); @@ -1055,12 +1046,40 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, return - END_CIRC_REASON_TORPROTOCOL; } + if (rh.stream_id == 0) { + switch (rh.command) { + case RELAY_COMMAND_BEGIN: + case RELAY_COMMAND_CONNECTED: + case RELAY_COMMAND_DATA: + case RELAY_COMMAND_END: + case RELAY_COMMAND_RESOLVE: + case RELAY_COMMAND_RESOLVED: + case RELAY_COMMAND_BEGIN_DIR: + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "Relay command %d with zero " + "stream_id. Dropping.", (int)rh.command); + return 0; + default: + ; + } + } + /* either conn is NULL, in which case we've got a control cell, or else * conn points to the recognized stream. */ - if (conn && !connection_state_is_open(TO_CONN(conn))) - return connection_edge_process_relay_cell_not_open( - &rh, cell, circ, conn, layer_hint); + if (conn && !connection_state_is_open(TO_CONN(conn))) { + if (conn->_base.type == CONN_TYPE_EXIT && + (conn->_base.state == EXIT_CONN_STATE_CONNECTING || + conn->_base.state == EXIT_CONN_STATE_RESOLVING) && + rh.command == RELAY_COMMAND_DATA) { + /* Allow DATA cells to be delivered to an exit node in state + * EXIT_CONN_STATE_CONNECTING or EXIT_CONN_STATE_RESOLVING. + * This speeds up HTTP, for example. */ + optimistic_data = 1; + } else { + return connection_edge_process_relay_cell_not_open( + &rh, cell, circ, conn, layer_hint); + } + } switch (rh.command) { case RELAY_COMMAND_DROP: @@ -1103,8 +1122,12 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, (!layer_hint && --circ->deliver_window < 0)) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, "(relay data) circ deliver_window below 0. Killing."); - connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); - connection_mark_for_close(TO_CONN(conn)); + if (conn) { + /* XXXX Do we actually need to do this? Will killing the circuit + * not send an END and mark the stream for close as appropriate? */ + connection_edge_end(conn, END_STREAM_REASON_TORPROTOCOL); + connection_mark_for_close(TO_CONN(conn)); + } return -END_CIRC_REASON_TORPROTOCOL; } log_debug(domain,"circ deliver_window now %d.", layer_hint ? @@ -1127,7 +1150,14 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, stats_n_data_bytes_received += rh.length; connection_write_to_buf((char*)(cell->payload + RELAY_HEADER_SIZE), rh.length, TO_CONN(conn)); - connection_edge_consider_sending_sendme(conn); + + if (!optimistic_data) { + /* Only send a SENDME if we're not getting optimistic data; otherwise + * a SENDME could arrive before the CONNECTED. + */ + connection_edge_consider_sending_sendme(conn); + } + return 0; case RELAY_COMMAND_END: reason = rh.length > 0 ? @@ -1142,9 +1172,13 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, conn->_base.s, stream_end_reason_to_string(reason), conn->stream_id); - if (conn->socks_request && !conn->socks_request->has_finished) - log_warn(LD_BUG, - "open stream hasn't sent socks answer yet? Closing."); + if (conn->_base.type == CONN_TYPE_AP) { + entry_connection_t *entry_conn = EDGE_TO_ENTRY_CONN(conn); + if (entry_conn->socks_request && + !entry_conn->socks_request->has_finished) + log_warn(LD_BUG, + "open stream hasn't sent socks answer yet? Closing."); + } /* We just *got* an end; no reason to send one. */ conn->edge_has_sent_end = 1; if (!conn->end_reason) @@ -1152,17 +1186,43 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, if (!conn->_base.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(TO_CONN(conn)); - conn->_base.hold_open_until_flushed = 1; + connection_mark_and_flush(TO_CONN(conn)); } return 0; - case RELAY_COMMAND_EXTEND: + case RELAY_COMMAND_EXTEND: { + static uint64_t total_n_extend=0, total_nonearly=0; + total_n_extend++; if (conn) { log_fn(LOG_PROTOCOL_WARN, domain, "'extend' cell received for non-zero stream. Dropping."); return 0; } + if (cell->command != CELL_RELAY_EARLY && + !networkstatus_get_param(NULL,"AllowNonearlyExtend",0,0,1)) { +#define EARLY_WARNING_INTERVAL 3600 + static ratelim_t early_warning_limit = + RATELIM_INIT(EARLY_WARNING_INTERVAL); + char *m; + if (cell->command == CELL_RELAY) { + ++total_nonearly; + if ((m = rate_limit_log(&early_warning_limit, approx_time()))) { + double percentage = ((double)total_nonearly)/total_n_extend; + percentage *= 100; + log_fn(LOG_PROTOCOL_WARN, domain, "EXTEND cell received, " + "but not via RELAY_EARLY. Dropping.%s", m); + log_fn(LOG_PROTOCOL_WARN, domain, " (We have dropped %.02f%% of " + "all EXTEND cells for this reason)", percentage); + tor_free(m); + } + } else { + log_fn(LOG_WARN, domain, + "EXTEND cell received, in a cell with type %d! Dropping.", + cell->command); + } + return 0; + } return circuit_extend(cell, circ); + } case RELAY_COMMAND_EXTENDED: if (!layer_hint) { log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, @@ -1220,13 +1280,27 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, "'connected' received, no conn attached anymore. Ignoring."); return 0; case RELAY_COMMAND_SENDME: - if (!conn) { + if (!rh.stream_id) { if (layer_hint) { + if (layer_hint->package_window + CIRCWINDOW_INCREMENT > + CIRCWINDOW_START_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Bug/attack: unexpected sendme cell from exit relay. " + "Closing circ."); + return -END_CIRC_REASON_TORPROTOCOL; + } layer_hint->package_window += CIRCWINDOW_INCREMENT; log_debug(LD_APP,"circ-level sendme at origin, packagewindow %d.", layer_hint->package_window); circuit_resume_edge_reading(circ, layer_hint); } else { + if (circ->package_window + CIRCWINDOW_INCREMENT > + CIRCWINDOW_START_MAX) { + log_fn(LOG_PROTOCOL_WARN, LD_PROTOCOL, + "Bug/attack: unexpected sendme cell from client. " + "Closing circ."); + return -END_CIRC_REASON_TORPROTOCOL; + } circ->package_window += CIRCWINDOW_INCREMENT; log_debug(LD_APP, "circ-level sendme at non-origin, packagewindow %d.", @@ -1235,6 +1309,11 @@ connection_edge_process_relay_cell(cell_t *cell, circuit_t *circ, } return 0; } + if (!conn) { + log_info(domain,"sendme cell dropped, unknown stream (streamid %d).", + rh.stream_id); + return 0; + } conn->package_window += STREAMWINDOW_INCREMENT; log_debug(domain,"stream-level sendme, packagewindow now %d.", conn->package_window); @@ -1324,10 +1403,17 @@ int connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, int *max_cells) { - size_t amount_to_process, length; + size_t bytes_to_process, length; char payload[CELL_PAYLOAD_SIZE]; circuit_t *circ; - unsigned domain = conn->cpath_layer ? LD_APP : LD_EXIT; + const unsigned domain = conn->_base.type == CONN_TYPE_AP ? LD_APP : LD_EXIT; + int sending_from_optimistic = 0; + const int sending_optimistically = + conn->_base.type == CONN_TYPE_AP && + conn->_base.state != AP_CONN_STATE_OPEN; + entry_connection_t *entry_conn = + conn->_base.type == CONN_TYPE_AP ? EDGE_TO_ENTRY_CONN(conn) : NULL; + crypt_path_t *cpath_layer = conn->cpath_layer; tor_assert(conn); @@ -1350,7 +1436,7 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, return -1; } - if (circuit_consider_stop_edge_reading(circ, conn->cpath_layer)) + if (circuit_consider_stop_edge_reading(circ, cpath_layer)) return 0; if (conn->package_window <= 0) { @@ -1360,44 +1446,75 @@ connection_edge_package_raw_inbuf(edge_connection_t *conn, int package_partial, return 0; } - amount_to_process = buf_datalen(conn->_base.inbuf); + sending_from_optimistic = entry_conn && + entry_conn->sending_optimistic_data != NULL; + + if (PREDICT_UNLIKELY(sending_from_optimistic)) { + bytes_to_process = generic_buffer_len(entry_conn->sending_optimistic_data); + if (PREDICT_UNLIKELY(!bytes_to_process)) { + log_warn(LD_BUG, "sending_optimistic_data was non-NULL but empty"); + bytes_to_process = connection_get_inbuf_len(TO_CONN(conn)); + sending_from_optimistic = 0; + } + } else { + bytes_to_process = connection_get_inbuf_len(TO_CONN(conn)); + } - if (!amount_to_process) + if (!bytes_to_process) return 0; - if (!package_partial && amount_to_process < RELAY_PAYLOAD_SIZE) + if (!package_partial && bytes_to_process < RELAY_PAYLOAD_SIZE) return 0; - if (amount_to_process > RELAY_PAYLOAD_SIZE) { + if (bytes_to_process > RELAY_PAYLOAD_SIZE) { length = RELAY_PAYLOAD_SIZE; } else { - length = amount_to_process; + length = bytes_to_process; } stats_n_data_bytes_packaged += length; stats_n_data_cells_packaged += 1; - connection_fetch_from_buf(payload, length, TO_CONN(conn)); + if (PREDICT_UNLIKELY(sending_from_optimistic)) { + /* XXXX We could be more efficient here by sometimes packing + * previously-sent optimistic data in the same cell with data + * from the inbuf. */ + generic_buffer_get(entry_conn->sending_optimistic_data, payload, length); + if (!generic_buffer_len(entry_conn->sending_optimistic_data)) { + generic_buffer_free(entry_conn->sending_optimistic_data); + entry_conn->sending_optimistic_data = NULL; + } + } else { + connection_fetch_from_buf(payload, length, TO_CONN(conn)); + } log_debug(domain,"(%d) Packaging %d bytes (%d waiting).", conn->_base.s, - (int)length, (int)buf_datalen(conn->_base.inbuf)); + (int)length, (int)connection_get_inbuf_len(TO_CONN(conn))); + + if (sending_optimistically && !sending_from_optimistic) { + /* This is new optimistic data; remember it in case we need to detach and + retry */ + if (!entry_conn->pending_optimistic_data) + entry_conn->pending_optimistic_data = generic_buffer_new(); + generic_buffer_add(entry_conn->pending_optimistic_data, payload, length); + } if (connection_edge_send_command(conn, RELAY_COMMAND_DATA, payload, length) < 0 ) /* circuit got marked for close, don't continue, don't need to mark conn */ return 0; - if (!conn->cpath_layer) { /* non-rendezvous exit */ + if (!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--; + tor_assert(cpath_layer->package_window > 0); + cpath_layer->package_window--; } if (--conn->package_window <= 0) { /* is it 0 after decrement? */ connection_stop_reading(TO_CONN(conn)); log_debug(domain,"conn->package_window reached 0."); - circuit_consider_stop_edge_reading(circ, conn->cpath_layer); + circuit_consider_stop_edge_reading(circ, cpath_layer); return 0; /* don't process the inbuf any more */ } log_debug(domain,"conn->package_window is now %d",conn->package_window); @@ -1436,7 +1553,7 @@ connection_edge_consider_sending_sendme(edge_connection_t *conn) } while (conn->deliver_window <= STREAMWINDOW_START - STREAMWINDOW_INCREMENT) { - log_debug(conn->cpath_layer?LD_APP:LD_EXIT, + log_debug(conn->_base.type == CONN_TYPE_AP ?LD_APP:LD_EXIT, "Outbuf %d, Queuing stream sendme.", (int)conn->_base.outbuf_flushlen); conn->deliver_window += STREAMWINDOW_INCREMENT; @@ -1532,7 +1649,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1543,7 +1660,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, if (!layer_hint || conn->cpath_layer == layer_hint) { connection_start_reading(TO_CONN(conn)); - if (buf_datalen(conn->_base.inbuf) > 0) + if (connection_get_inbuf_len(TO_CONN(conn)) > 0) ++n_packaging_streams; } } @@ -1582,7 +1699,7 @@ circuit_resume_edge_reading_helper(edge_connection_t *first_conn, } /* If there's still data to read, we'll be coming back to this stream. */ - if (buf_datalen(conn->_base.inbuf)) + if (connection_get_inbuf_len(TO_CONN(conn))) ++n_streams_left; /* If the circuit won't accept any more data, return without looking @@ -1638,9 +1755,10 @@ circuit_consider_stop_edge_reading(circuit_t *circ, crypt_path_t *layer_hint) if (layer_hint->package_window <= 0) { log_debug(domain,"yes, at-origin. stopped."); for (conn = TO_ORIGIN_CIRCUIT(circ)->p_streams; conn; - conn=conn->next_stream) + conn=conn->next_stream) { if (conn->cpath_layer == layer_hint) connection_stop_reading(TO_CONN(conn)); + } return 1; } return 0; @@ -1681,7 +1799,7 @@ circuit_consider_sending_sendme(circuit_t *circ, crypt_path_t *layer_hint) #endif /** The total number of cells we have allocated from the memory pool. */ -static int total_cells_allocated = 0; +static size_t total_cells_allocated = 0; /** A memory pool to allocate packed_cell_t objects. */ static mp_pool_t *cell_pool = NULL; @@ -1753,7 +1871,7 @@ dump_cell_pool_usage(int severity) ++n_circs; } log(severity, LD_MM, "%d cells allocated on %d circuits. %d cells leaked.", - n_cells, n_circs, total_cells_allocated - n_cells); + n_cells, n_circs, (int)total_cells_allocated - n_cells); mp_pool_log_status(cell_pool, severity); } @@ -1860,6 +1978,29 @@ cell_queue_pop(cell_queue_t *queue) return cell; } +/** Return the total number of bytes used for each packed_cell in a queue. + * Approximate. */ +size_t +packed_cell_mem_cost(void) +{ + return sizeof(packed_cell_t) + MP_POOL_ITEM_OVERHEAD + + get_options()->CellStatistics ? + (sizeof(insertion_time_elem_t)+MP_POOL_ITEM_OVERHEAD) : 0; +} + +/** Check whether we've got too much space used for cells. If so, + * call the OOM handler and return 1. Otherwise, return 0. */ +static int +cell_queues_check_size(void) +{ + size_t alloc = total_cells_allocated * packed_cell_mem_cost(); + if (alloc >= get_options()->MaxMemInCellQueues) { + circuits_handle_oom(alloc); + return 1; + } + return 0; +} + /** Return a pointer to the "next_active_on_{n,p}_conn" pointer of <b>circ</b>, * depending on whether <b>conn</b> matches n_conn or p_conn. */ static INLINE circuit_t ** @@ -1988,14 +2129,18 @@ cell_ewma_get_tick(void) * has value ewma_scale_factor ** N.) */ static double ewma_scale_factor = 0.1; +/* DOCDOC ewma_enabled */ static int ewma_enabled = 0; +/*DOCDOC*/ #define EPSILON 0.00001 +/*DOCDOC*/ #define LOG_ONEHALF -0.69314718055994529 /** Adjust the global cell scale factor based on <b>options</b> */ void -cell_ewma_set_scale_factor(or_options_t *options, networkstatus_t *consensus) +cell_ewma_set_scale_factor(const or_options_t *options, + const networkstatus_t *consensus) { int32_t halflife_ms; double halflife; @@ -2246,7 +2391,7 @@ set_streams_blocked_on_circ(circuit_t *circ, or_connection_t *orconn, edge->edge_blocked_on_circ = block; } - if (!conn->read_event) { + if (!conn->read_event && !HAS_BUFFEREVENT(conn)) { /* This connection is a placeholder for something; probably a DNS * request. It can't actually stop or start reading.*/ continue; @@ -2321,13 +2466,13 @@ connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, /* Calculate the exact time that this cell has spent in the queue. */ if (get_options()->CellStatistics && !CIRCUIT_IS_ORIGIN(circ)) { - struct timeval now; + struct timeval tvnow; uint32_t flushed; uint32_t cell_waiting_time; insertion_time_queue_t *it_queue = queue->insertion_times; - tor_gettimeofday_cached(&now); - flushed = (uint32_t)((now.tv_sec % SECONDS_IN_A_DAY) * 100L + - (uint32_t)now.tv_usec / (uint32_t)10000L); + tor_gettimeofday_cached(&tvnow); + flushed = (uint32_t)((tvnow.tv_sec % SECONDS_IN_A_DAY) * 100L + + (uint32_t)tvnow.tv_usec / (uint32_t)10000L); if (!it_queue || !it_queue->first) { log_info(LD_GENERAL, "Cannot determine insertion time of cell. " "Looks like the CellStatistics option was " @@ -2373,7 +2518,7 @@ connection_or_flush_from_first_active_circuit(or_connection_t *conn, int max, tor_assert(tmp == cell_ewma); add_cell_ewma_to_conn(conn, cell_ewma); } - if (circ != conn->active_circuits) { + if (!ewma_enabled && circ != conn->active_circuits) { /* If this happens, the current circuit just got made inactive by * a call in connection_write_to_buf(). That's nothing to worry about: * circuit_make_inactive_on_conn() already advanced conn->active_circuits @@ -2410,8 +2555,10 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, cell_t *cell, cell_direction_t direction, streamid_t fromstream) { + or_circuit_t *orcirc = NULL; cell_queue_t *queue; int streams_blocked; + if (circ->marked_for_close) return; @@ -2419,13 +2566,43 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, queue = &circ->n_conn_cells; streams_blocked = circ->streams_blocked_on_n_conn; } else { - or_circuit_t *orcirc = TO_OR_CIRCUIT(circ); + orcirc = TO_OR_CIRCUIT(circ); queue = &orcirc->p_conn_cells; streams_blocked = circ->streams_blocked_on_p_conn; } + /* + * Disabling this for now because of a possible guard discovery attack + */ +#if 0 + /* Are we a middle circuit about to exceed ORCIRC_MAX_MIDDLE_CELLS? */ + if ((circ->n_conn != NULL) && CIRCUIT_IS_ORCIRC(circ)) { + orcirc = TO_OR_CIRCUIT(circ); + if (orcirc->p_conn) { + if (queue->n + 1 >= ORCIRC_MAX_MIDDLE_CELLS) { + /* Queueing this cell would put queue over the cap */ + log_warn(LD_CIRC, + "Got a cell exceeding the cap of %u in the %s direction " + "on middle circ ID %u; killing the circuit.", + ORCIRC_MAX_MIDDLE_CELLS, + (direction == CELL_DIRECTION_OUT) ? "n" : "p", + (direction == CELL_DIRECTION_OUT) ? + circ->n_circ_id : orcirc->p_circ_id); + circuit_mark_for_close(circ, END_CIRC_REASON_RESOURCELIMIT); + return; + } + } + } +#endif + cell_queue_append_packed_copy(queue, cell); + if (PREDICT_UNLIKELY(cell_queues_check_size())) { + /* We ran the OOM handler */ + if (circ->marked_for_close) + return; + } + /* If we have too many cells on the circuit, we should stop reading from * the edge streams for a while. */ if (!streams_blocked && queue->n >= CELL_QUEUE_HIGHWATER_SIZE) @@ -2443,7 +2620,7 @@ append_cell_to_circuit_queue(circuit_t *circ, or_connection_t *orconn, make_circuit_active_on_conn(circ, orconn); } - if (! buf_datalen(orconn->_base.outbuf)) { + if (! connection_get_outbuf_len(TO_CONN(orconn))) { /* There is no data at all waiting to be sent on the outbuf. Add a * cell, so that we can notice when it gets flushed, flushed_some can * get called, and we can start putting more data onto the buffer then. |