[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

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



-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA512

Hello.

On 2026-09-13 08:44, Toralf Förster via tor-relays wrote:
> 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
-----BEGIN PGP SIGNATURE-----

iHUEARYKAB0WIQQtr8ZXhq/o01Qf/pow+TRLM+X4xgUCaqZrXQAKCRAw+TRLM+X4
xh4CAP9/9SQeJZE/Um/6EeJOU8JIwEm1Fjpk+VRuzfFJLnDxzgD+OKyklSOoeULx
LyEng4AzFxOJqlAVdORCOw97CLIlzwY=
=0YYx
-----END PGP SIGNATURE-----
_______________________________________________
tor-relays mailing list -- tor-relays@xxxxxxxxxxxxxxxxxxxx
To unsubscribe send an email to tor-relays-leave@xxxxxxxxxxxxxxxxxxxx