diff --git a/src/core/or/circuitlist.c b/src/core/or/circuitlist.c
index b90c7ebb58..643d97b064 100644
--- a/src/core/or/circuitlist.c
+++ b/src/core/or/circuitlist.c
@@ -65,6 +65,7 @@
#include "core/or/conflux.h"
#include "core/or/conflux_pool.h"
#include "core/or/crypt_path.h"
+#include "core/or/dos.h"
#include "core/or/extendinfo.h"
#include "core/or/status.h"
#include "core/or/trace_probes_circuit.h"
@@ -1130,6 +1131,7 @@ or_circuit_new(circid_t p_circ_id, channel_t *p_chan)
cell_queue_init(&circ->p_chan_cells);
init_circuit_base(TO_CIRCUIT(circ));
+ dos_stream_init_circ_tbf(circ);
tor_trace(TR_SUBSYS(circuit), TR_EV(new_or), circ);
return circ;
diff --git a/src/core/or/connection_edge.c b/src/core/or/connection_edge.c
index 900d639959..764e1c886b 100644
--- a/src/core/or/connection_edge.c
+++ b/src/core/or/connection_edge.c
@@ -73,6 +73,7 @@
#include "core/or/conflux_util.h"
#include "core/or/circuitstats.h"
#include "core/or/connection_or.h"
+#include "core/or/dos.h"
#include "core/or/extendinfo.h"
#include "core/or/policies.h"
#include "core/or/reasons.h"
@@ -3990,6 +3991,7 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
begin_cell_t bcell;
int rv;
uint8_t end_reason=0;
+ dos_stream_defense_type_t dos_defense_type;
assert_circuit_ok(circ);
if (!CIRCUIT_IS_ORIGIN(circ)) {
@@ -4148,6 +4150,35 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
log_debug(LD_EXIT,"about to start the dns_resolve().");
+ /* TODO should this be moved higher to protect from a stream DoS on directory
+ * requests, and possibly against an onion service? (for OS, more changes
+ * would be required) */
+ dos_defense_type = dos_stream_new_begin_or_resolve_cell(or_circ);
+ switch (dos_defense_type) {
+ case DOS_STREAM_DEFENSE_NONE:
+ break;
+ case DOS_STREAM_DEFENSE_REFUSE_STREAM:
+ // we don't use END_STREAM_REASON_RESOURCELIMIT because it would make a
+ // client mark us as non-functional until they get a new consensus.
+ relay_send_end_cell_from_edge(rh.stream_id, circ, END_STREAM_REASON_MISC,
+ layer_hint);
+ connection_free_(TO_CONN(n_stream));
+ return 0;
+ case DOS_STREAM_DEFENSE_CLOSE_CIRCUIT:
+ connection_free_(TO_CONN(n_stream));
+ /* TODO we could return REASON_NONE or REASON_RESOURCELIMIT. When closing
+ * circuits, you either get:
+ * - END_CIRC_REASON_NONE: tons of notice level "We tried for 15
+ * seconds to connect to 'target' using exit X. Retrying on a new
+ * circuit."
+ * - END_CIRC_REASON_RESOURCELIMIT: warn level "Guard X is failing
+ * to carry an extremely large amount of streams on its circuits"
+ *
+ * I'm not sure which one we want
+ */
+ return -END_CIRC_REASON_NONE;
+ }
+
/* send it off to the gethostbyname farm */
switch (dns_resolve(n_stream)) {
case 1: /* resolve worked; now n_stream is attached to circ. */
@@ -4171,17 +4202,21 @@ connection_exit_begin_conn(cell_t *cell, circuit_t *circ)
* Called when we receive a RELAY_COMMAND_RESOLVE cell 'cell' along the
* circuit circ;
* begin resolving the hostname, and (eventually) reply with a RESOLVED cell.
+ *
+ * Return -(some circuit end reason) if we want to tear down circ.
+ * Else return 0.
*/
int
connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ)
{
edge_connection_t *dummy_conn;
relay_header_t rh;
+ dos_stream_defense_type_t dos_defense_type;
assert_circuit_ok(TO_CIRCUIT(circ));
relay_header_unpack(&rh, cell->payload);
if (rh.length > RELAY_PAYLOAD_SIZE)
- return -1;
+ return 0;
/* Note the RESOLVE stream as seen. */
rep_hist_note_exit_stream(RELAY_COMMAND_RESOLVE);
@@ -4204,6 +4239,18 @@ connection_exit_begin_resolve(cell_t *cell, or_circuit_t *circ)
dummy_conn->on_circuit = TO_CIRCUIT(circ);
+ dos_defense_type = dos_stream_new_begin_or_resolve_cell(circ);
+ switch (dos_defense_type) {
+ case DOS_STREAM_DEFENSE_NONE:
+ break;
+ case DOS_STREAM_DEFENSE_REFUSE_STREAM:
+ dns_send_resolved_error_cell(dummy_conn, RESOLVED_TYPE_ERROR_TRANSIENT);
+ return 0;
+ case DOS_STREAM_DEFENSE_CLOSE_CIRCUIT:
+ /* TODO maybe use REASON_RESOURCELIMIT? See connection_exit_begin_conn() */
+ return -END_CIRC_REASON_NONE;
+ }
+
/* send it off to the gethostbyname farm */
switch (dns_resolve(dummy_conn)) {
case -1: /* Impossible to resolve; a resolved cell was sent. */
diff --git a/src/core/or/dos.c b/src/core/or/dos.c
index a47738c906..63cac190fd 100644
--- a/src/core/or/dos.c
+++ b/src/core/or/dos.c
@@ -325,7 +325,8 @@ get_param_stream_defense_type(const networkstatus_t *ns)
}
return networkstatus_get_param(ns, "DoSStreamCreationDefenseType",
DOS_STREAM_DEFENSE_TYPE_DEFAULT,
- DOS_STREAM_DEFENSE_NONE, DOS_STREAM_DEFENSE_MAX);
+ DOS_STREAM_DEFENSE_NONE,
+ DOS_STREAM_DEFENSE_MAX);
}
/* Set circuit creation parameters located in the consensus or their default
@@ -836,6 +837,41 @@ dos_conn_addr_get_defense_type(const tor_addr_t *addr)
return DOS_CONN_DEFENSE_NONE;
}
+/* Stream creation public API. */
+
+/* Return the action to take against a BEGIN or RESOLVE cell. Return
+ * DOS_STREAM_DEFENSE_NONE when no action should be taken.
+ * Increment the appropriate counter when the cell was found to go over a
+ * limit. */
+dos_stream_defense_type_t
+dos_stream_new_begin_or_resolve_cell(or_circuit_t *circ)
+{
+ if (!dos_stream_enabled || circ == NULL)
+ return DOS_STREAM_DEFENSE_NONE;
+
+ token_bucket_ctr_refill(&circ->stream_limiter,
+ (uint32_t) monotime_coarse_absolute_sec());
+
+ if (token_bucket_ctr_get(&circ->stream_limiter) > 0) {
+ token_bucket_ctr_dec(&circ->stream_limiter, 1);
+ return DOS_STREAM_DEFENSE_NONE;
+ }
+ /* if defense type is DOS_STREAM_DEFENSE_NONE but DoSStreamEnabled is true,
+ * we count offending cells as rejected, despite them being actually
+ * accepted. */
+ ++stream_num_rejected;
+ return dos_stream_defense_type;
+}
+
+/* Initialize the token bucket for stream rate limit on a circuit. */
+void
+dos_stream_init_circ_tbf(or_circuit_t *circ)
+{
+ token_bucket_ctr_init(&circ->stream_limiter, dos_stream_rate,
+ dos_stream_burst,
+ (uint32_t) monotime_coarse_absolute_sec());
+}
+
/* General API */
/* Take any appropriate actions for the given geoip entry that is about to get
diff --git a/src/core/or/dos.h b/src/core/or/dos.h
index 8b36fe1415..77dce333d1 100644
--- a/src/core/or/dos.h
+++ b/src/core/or/dos.h
@@ -186,6 +186,10 @@ typedef enum dos_stream_defense_type_t {
DOS_STREAM_DEFENSE_MAX = 3,
} dos_stream_defense_type_t;
+dos_stream_defense_type_t dos_stream_new_begin_or_resolve_cell(
+ or_circuit_t *circ);
+void dos_stream_init_circ_tbf(or_circuit_t *circ);
+
#ifdef DOS_PRIVATE
STATIC uint32_t get_param_conn_max_concurrent_count(
diff --git a/src/core/or/or_circuit_st.h b/src/core/or/or_circuit_st.h
index d5a7007928..28e357338a 100644
--- a/src/core/or/or_circuit_st.h
+++ b/src/core/or/or_circuit_st.h
@@ -102,6 +102,10 @@ struct or_circuit_t {
* used if this is a service introduction circuit at the intro point
* (purpose = CIRCUIT_PURPOSE_INTRO_POINT). */
token_bucket_ctr_t introduce2_bucket;
+
+ /** RELAY_BEGIN and RELAY_RESOLVE cell bucket controlling how much can go on
+ * this circuit. Only used if this is the end of a circuit on an exit node.*/
+ token_bucket_ctr_t stream_limiter;
};
#endif /* !defined(OR_CIRCUIT_ST_H) */
diff --git a/src/core/or/relay.c b/src/core/or/relay.c
index 6abe802355..0cda6e7bf7 100644
--- a/src/core/or/relay.c
+++ b/src/core/or/relay.c
@@ -2015,8 +2015,7 @@ handle_relay_cell_command(cell_t *cell, circuit_t *circ,
circ->purpose);
return 0;
}
- connection_exit_begin_resolve(cell, TO_OR_CIRCUIT(circ));
- return 0;
+ return connection_exit_begin_resolve(cell, TO_OR_CIRCUIT(circ));
case RELAY_COMMAND_RESOLVED:
if (conn) {
log_fn(LOG_PROTOCOL_WARN, domain,
diff --git a/src/feature/relay/dns.c b/src/feature/relay/dns.c
index f6a020d061..129f6209d7 100644
--- a/src/feature/relay/dns.c
+++ b/src/feature/relay/dns.c
@@ -563,6 +563,12 @@ send_resolved_cell,(edge_connection_t *conn, uint8_t answer_type,
connection_edge_send_command(conn, RELAY_COMMAND_RESOLVED, buf, buflen);
}
+void
+dns_send_resolved_error_cell(edge_connection_t *conn, uint8_t answer_type)
+{
+ send_resolved_cell(conn, answer_type, NULL);
+}
+
/** Send a response to the RESOLVE request of a connection for an in-addr.arpa
* address on connection conn which yielded the result hostname.
* The answer type will be RESOLVED_HOSTNAME.
diff --git a/src/feature/relay/dns.h b/src/feature/relay/dns.h
index 3f8519bd97..b43b42756e 100644
--- a/src/feature/relay/dns.h
+++ b/src/feature/relay/dns.h
@@ -20,6 +20,8 @@ int dns_reset(void);
void connection_dns_remove(edge_connection_t *conn);
void assert_connection_edge_not_dns_pending(edge_connection_t *conn);
int dns_resolve(edge_connection_t *exitconn);
+void dns_send_resolved_error_cell(edge_connection_t *conn,
+ uint8_t answer_type);
int dns_seems_to_be_broken(void);
int dns_seems_to_be_broken_for_ipv6(void);
void dns_reset_correctness_checks(void);
diff --git a/src/test/test_status.c b/src/test/test_status.c
index 5873d8e5c3..4ceb81f3a5 100644
--- a/src/test/test_status.c
+++ b/src/test/test_status.c
@@ -365,6 +365,7 @@ test_status_hb_not_in_consensus(void *arg)
"with too many cells, [DoSCircuitCreationEnabled disabled], "
"[DoSConnectionEnabled disabled], "
"[DoSRefuseSingleHopClientRendezvous disabled], "
+ "[DoSStreamCreationEnabled disabled], "
"0 INTRODUCE2 rejected.\n");
tt_int_op(mock_saved_log_n_entries(), OP_EQ, 6);