|
@@ -35,6 +35,9 @@ static uint32_t dos_cc_circuit_burst;
|
|
|
static dos_cc_defense_type_t dos_cc_defense_type;
|
|
|
static int32_t dos_cc_defense_time_period;
|
|
|
|
|
|
+
|
|
|
+static uint32_t cc_num_marked_addrs;
|
|
|
+
|
|
|
|
|
|
* Concurrent connection denial of service mitigation.
|
|
|
*
|
|
@@ -209,6 +212,117 @@ cc_consensus_has_changed(const networkstatus_t *ns)
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ * configuration. */
|
|
|
+STATIC uint32_t
|
|
|
+get_circuit_rate_per_second(void)
|
|
|
+{
|
|
|
+ int64_t circ_rate;
|
|
|
+
|
|
|
+
|
|
|
+ * convert to get a circuit rate per second. */
|
|
|
+ circ_rate = dos_cc_circuit_rate_tenths / 10;
|
|
|
+ if (circ_rate < 0) {
|
|
|
+
|
|
|
+ * be empty resulting in every address to be detected. */
|
|
|
+ circ_rate = 1;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * second is just too much in any circumstances. */
|
|
|
+ if (circ_rate > UINT32_MAX) {
|
|
|
+ circ_rate = UINT32_MAX;
|
|
|
+ }
|
|
|
+ return (uint32_t) circ_rate;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * bucket if needed. This also works if the bucket was never filled in the
|
|
|
+ * first place. The addr is only used for logging purposes. */
|
|
|
+STATIC void
|
|
|
+cc_stats_refill_bucket(cc_client_stats_t *stats, const tor_addr_t *addr)
|
|
|
+{
|
|
|
+ uint32_t new_circuit_bucket_count, circuit_rate = 0, num_token;
|
|
|
+ time_t now, elapsed_time_last_refill;
|
|
|
+
|
|
|
+ tor_assert(stats);
|
|
|
+ tor_assert(addr);
|
|
|
+
|
|
|
+ now = approx_time();
|
|
|
+
|
|
|
+
|
|
|
+ * and we are done. */
|
|
|
+ if (stats->last_circ_bucket_refill_ts == 0) {
|
|
|
+ num_token = dos_cc_circuit_burst;
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * first compute the circuit rate that is how many circuit are we allowed to
|
|
|
+ * do per second. */
|
|
|
+ circuit_rate = get_circuit_rate_per_second();
|
|
|
+
|
|
|
+
|
|
|
+ elapsed_time_last_refill = now - stats->last_circ_bucket_refill_ts;
|
|
|
+
|
|
|
+
|
|
|
+ * that case, lets be safe and fill it up to the maximum. Not filling it
|
|
|
+ * could trigger a detection for a valid client. Also, if the clock jumped
|
|
|
+ * negative but we didn't notice until the elapsed time became positive
|
|
|
+ * again, then we potentially spent many seconds not refilling the bucket
|
|
|
+ * when we should have been refilling it. But the fact that we didn't notice
|
|
|
+ * until now means that no circuit creation requests came in during that
|
|
|
+ * time, so the client doesn't end up punished that much from this hopefully
|
|
|
+ * rare situation.*/
|
|
|
+ if (elapsed_time_last_refill < 0) {
|
|
|
+
|
|
|
+ * give us the maximum allowed value of token. */
|
|
|
+ elapsed_time_last_refill = (dos_cc_circuit_burst / circuit_rate);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * add to the bucket. This can be big but it is cap to a maximum after. */
|
|
|
+ num_token = elapsed_time_last_refill * circuit_rate;
|
|
|
+
|
|
|
+ end:
|
|
|
+
|
|
|
+ * over time. */
|
|
|
+ new_circuit_bucket_count = MIN(stats->circuit_bucket + num_token,
|
|
|
+ dos_cc_circuit_burst);
|
|
|
+ log_debug(LD_DOS, "DoS address %s has its circuit bucket value: %" PRIu32
|
|
|
+ ". Filling it to %" PRIu32 ". Circuit rate is %" PRIu32,
|
|
|
+ fmt_addr(addr), stats->circuit_bucket, new_circuit_bucket_count,
|
|
|
+ circuit_rate);
|
|
|
+
|
|
|
+ stats->circuit_bucket = new_circuit_bucket_count;
|
|
|
+ stats->last_circ_bucket_refill_ts = now;
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * concurrent connections is greater or equal the minimum threshold set the
|
|
|
+ * consensus parameter. */
|
|
|
+static int
|
|
|
+cc_has_exhausted_circuits(const dos_client_stats_t *stats)
|
|
|
+{
|
|
|
+ tor_assert(stats);
|
|
|
+ return stats->cc_stats.circuit_bucket == 0 &&
|
|
|
+ stats->concurrent_count >= dos_cc_min_concurrent_conn;
|
|
|
+}
|
|
|
+
|
|
|
+
|
|
|
+ * us until when it is marked as positively detected. */
|
|
|
+static void
|
|
|
+cc_mark_client(cc_client_stats_t *stats)
|
|
|
+{
|
|
|
+ tor_assert(stats);
|
|
|
+
|
|
|
+ * less predictable. */
|
|
|
+ stats->marked_until_ts =
|
|
|
+ approx_time() + dos_cc_defense_time_period +
|
|
|
+ crypto_rand_int_range(1, dos_cc_defense_time_period / 2);
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
|
|
|
|
|
@@ -242,6 +356,71 @@ dos_is_enabled(void)
|
|
|
|
|
|
|
|
|
|
|
|
+
|
|
|
+void
|
|
|
+dos_cc_new_create_cell(channel_t *chan)
|
|
|
+{
|
|
|
+ tor_addr_t addr;
|
|
|
+ clientmap_entry_t *entry;
|
|
|
+
|
|
|
+ tor_assert(chan);
|
|
|
+
|
|
|
+
|
|
|
+ if (!dos_cc_enabled) {
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ if (!channel_is_client(chan)) {
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!channel_get_addr_if_possible(chan, &addr)) {
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ entry = geoip_lookup_client(&addr, NULL, GEOIP_CLIENT_CONNECT);
|
|
|
+ if (entry == NULL) {
|
|
|
+
|
|
|
+ * cache. Once this DoS subsystem is enabled, we can end up here with no
|
|
|
+ * entry for the channel. */
|
|
|
+ goto end;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * malicious, we continue to track statistics. If it keeps going above
|
|
|
+ * threshold while marked, the defense period time will grow longer. There
|
|
|
+ * is really no point at unmarking a client that keeps DoSing us. */
|
|
|
+
|
|
|
+
|
|
|
+ * before we assess. */
|
|
|
+ cc_stats_refill_bucket(&entry->dos_stats.cc_stats, &addr);
|
|
|
+
|
|
|
+
|
|
|
+ * underflow the bucket. */
|
|
|
+ if (entry->dos_stats.cc_stats.circuit_bucket > 0) {
|
|
|
+ entry->dos_stats.cc_stats.circuit_bucket--;
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ * get marked as malicious. This should be kept as fast as possible. */
|
|
|
+ if (cc_has_exhausted_circuits(&entry->dos_stats)) {
|
|
|
+
|
|
|
+ * Under heavy DDoS, logging each time we mark would results in lots and
|
|
|
+ * lots of logs. */
|
|
|
+ if (entry->dos_stats.cc_stats.marked_until_ts == 0) {
|
|
|
+ log_debug(LD_DOS, "Detected circuit creation DoS by address: %s",
|
|
|
+ fmt_addr(&addr));
|
|
|
+ cc_num_marked_addrs++;
|
|
|
+ }
|
|
|
+ cc_mark_client(&entry->dos_stats.cc_stats);
|
|
|
+ }
|
|
|
+
|
|
|
+ end:
|
|
|
+ return;
|
|
|
+}
|
|
|
+
|
|
|
|
|
|
|
|
|
|