[tor-relays] Re: DDoS solution for Tor nodes - nftables variant implemented

Hello.

I eventually managed to have it [1] for nftables too and will continue to
maintain both variants.

I wrote an nftables port as well, with some adjustments. It relies on
populating an nftables set with the list of known relays (overriding the
default /0 whitelist) using a script in cron.daily:

  #!/bin/sh

  test -s /var/lib/tor/cached-consensus || exit 1

  runuser -u debian-tor awk '
          /^r / { v4 = v4 (v4 ? ", " : "") $7 }
          /^a / { match($2 ,/\[[^]]+\]/); v6 = v6 (v6 ? ", " : "")
substr($2, RSTART+1, RLENGTH-2) }
          BEGIN {
                  print "flush set inet filter known_relays4"
                  print "flush set inet filter known_relays6"
          }
          END {
                  if (v4) print "add element inet filter known_relays4 {
" v4 " }"
                  if (v6) print "add element inet filter known_relays6 {
" v6 " }"
          }
  ' /var/lib/tor/cached-consensus | nft -f -

Below is the nftables include file, which gets referenced in the input
chain with "tcp dport 9001 jump tor_input". The script is similar to
yours but it implicitly trusts Tor relays and is more aggressive with
non-Tor connections, batching together entire /24s. It doesn't seem like
it's realistically causing many false positives, since it's highly
unlikely that more than 8 new connections will be initiated from within
the same /24 within a single minute legitimately (or 32 in two hours):

  define mask4_24 = 255.255.255.0
  define mask6_64 = ffff:ffff:ffff:ffff::

  set tor_ddos4 {
          type ipv4_addr
          flags timeout
          timeout 24h
  }

  set tor_ddos6 {
          type ipv6_addr
          flags timeout
          timeout 24h
  }

  set tor_connlimit4 {
          type ipv4_addr
  }

  set tor_connlimit6 {
          type ipv6_addr
  }

  set known_relays4 {
          type ipv4_addr
          flags interval
          auto-merge
          elements = { 0.0.0.0/0 }
  }

  set known_relays6 {
          type ipv6_addr
          flags interval
          auto-merge
          elements = { ::/0 }
  }

  define tor_trusted4 = {
          # snowflake servers
          141.212.118.18,
          193.187.88.42,
          193.187.88.43,
          193.187.88.44,
          193.187.88.45,
          193.187.88.46,
          # directory authorities
          45.66.35.11,
          66.111.2.131,
          128.31.0.39,
          131.188.40.189,
          171.25.193.9,
          193.23.244.244,
          199.58.81.140,
          204.13.164.118,
          216.218.219.41,
          217.196.147.77
  }

  define tor_trusted6 = {
          # snowflake servers
          2a0c:dd40:1:b::42,
          2607:f018:600:8:be30:5bff:fef1:c6fa,
          # directory authorities
          2001:470:164:2::2,
          2001:638:a000:4140::ffff:189,
          2001:678:558:1000::244,
          2001:67c:289c::9,
          2610:1c0:0:5::131,
          2620:13:4000:6000::1000:118,
          2a02:16a8:662:2203::1
  }

  chain tor_input4 {
          ip saddr $tor_trusted4 accept
          ip saddr @known_relays4 jump {
                  add @tor_connlimit4 { ip saddr ct count over 16 } drop
                  accept
          }
          tcp flags syn / syn,ack jump {
                  meter tor_ddos_fast4 { ip saddr & $mask4_24 timeout 2m
limit rate over 8/minute burst 8 packets } update @tor_ddos4 { ip saddr
& $mask4_24 }
                  meter tor_ddos_slow4 { ip saddr & $mask4_24 timeout 1h
limit rate over 32/hour burst 32 packets } update @tor_ddos4 { ip saddr
& $mask4_24 }
          }
          ip saddr & $mask4_24 @tor_ddos4 drop
          tcp flags syn / syn,ack add @tor_connlimit4 { ip saddr &
$mask4_24 ct count over 20 } drop
          accept
  }

  chain tor_input6 {
          ip6 saddr $tor_trusted6 accept
          ip6 saddr @known_relays6 jump {
                  add @tor_connlimit6 { ip6 saddr ct count over 16 }
drop
                  accept
          }
          tcp flags syn / syn,ack jump {
                  meter tor_ddos_fast6 { ip6 saddr & $mask6_64 timeout
2m limit rate over 8/minute burst 8 packets } update @tor_ddos6 { ip6
saddr & $mask6_64 }
                  meter tor_ddos_slow6 { ip6 saddr & $mask6_64 timeout
1h limit rate over 32/hour burst 32 packets } update @tor_ddos6 { ip6
saddr & $mask6_64 }
          }
          ip6 saddr & $mask6_64 @tor_ddos6 drop
          tcp flags syn / syn,ack add @tor_connlimit6 { ip6 saddr &
$mask6_64 ct count over 20 } drop
          accept
  }

  chain tor_input {
          # requiring syn effectively disables loose connection tracking
(nf_conntrack_tcp_loose) for this chain
          ct state new tcp flags syn jump {
                  meta nfproto ipv4 jump tor_input4
                  meta nfproto ipv6 jump tor_input6
          }
          drop
  }

  chain tor_kill_established {
          type filter hook input priority filter - 1; policy accept;

          ct state established tcp dport 9001 jump {
                  ip saddr & $mask4_24 @tor_ddos4 drop
                  ip6 saddr & $mask6_64 @tor_ddos6 drop
          }
  }

In my testing, this reduced load on relays under attack slightly more
than the original, primarily because it could get away with stricter
thresholds without worrying about accidentally blocking relays.

Regards,
forest

···

On 2026-09-13 08:44, Toralf Förster via tor-relays wrote:

_______________________________________________
tor-relays mailing list -- tor-relays@lists.torproject.org
To unsubscribe send an email to tor-relays-leave@lists.torproject.org