add rate limit on BEGIN and RESOLVE cell per circuit

This commit is contained in:
trinity-1686a 2023-09-10 13:13:11 +02:00 committed by David Goulet
parent 379fb329d9
commit 1b907d13bb
9 changed files with 105 additions and 4 deletions

View File

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

View File

@ -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 <b>circ</b>;
* begin resolving the hostname, and (eventually) reply with a RESOLVED cell.
*
* Return -(some circuit end reason) if we want to tear down <b>circ</b>.
* 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. */

View File

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

View File

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

View File

@ -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) */

View File

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

View File

@ -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 <b>conn</b> which yielded the result <b>hostname</b>.
* The answer type will be RESOLVED_HOSTNAME.

View File

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

View File

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