This is a guide to my personal solution to the Tor netscan problem, which can affect the reputation of exit nodes. The solution uses ufw, fail2ban, and systemd-journal, but can be adapted to suit your needs. This approach is not perfect, but it helps prevent netscan abuse while still supporting the Tor network.
The solution works by temporarily banning outgoing traffic to a specific port if too many connections are made in a short period of time. This may affect some legitimate traffic, but due to the anonymous nature of Tor, it's not possible to block specific traffic sources. The assumption is that netscans are occasional, and most of the time, legitimate traffic will not be filtered.
- ufw logs all new outgoing connections to the journal.
- fail2ban monitors the journal, parses ufw logs, and compares the destination port with a predetermined list. If it matches, a counter is incremented.
- If too many connections are made within a short period of time, fail2ban executes a ufw command to temporarily block outgoing traffic to that port.
First, you need to ensure that ufw is properly configured to log the necessary information. The logging level should be set to at least high.
ufw status verbose
# Status: active
# Logging: on (high) <---
# Default: deny (incoming), allow (outgoing), deny (routed)
# …
If not, configure it by executing ufw logging high
.
After confirming the logging level, verify that ufw logs are being registered correctly, particularly the UFW ALLOW.
journalctl -kr -n30
# Apr 24 04:44:56 hostname kernel: [UFW ALLOW] IN= OUT=eth0 SRC=1.2.3.4 DST=5.6.7.8 LEN=60 TOS=0x00 PREC=0x00 TTL=64 ID=1 DF PROTO=TCP SPT=54321 DPT=22 WINDOW=1 RES=0x00 SYN URGP=0
# …
To configure fail2ban, you need to create and edit three configuration files for the filter, action, and jail settings.
- filter configuration: filter.d/tor-netscan.conf
[INCLUDES]
before = common.conf
[Definition]
_daemon = kernel
prefregex = ^%(__prefix_line)s\[\d+\.\d+\] <F-CONTENT>.+</F-CONTENT>$
failregex = ^\[UFW (?:ALLOW|AUDIT(?: INVALID)?|BLOCK)\] IN= .*?DPT=<F-ID><DPT></F-ID>\b
ignoreregex =
maxlines = 1
- action configuration: action.d/tor-netscan.conf
[Definition]
actionstart =
actionstop =
actioncheck =
actionban = ufw reject out to any port <F-ID> comment "(Automatic) fail2ban-<name> : <time>"
actionunban = ufw delete reject out to any port <F-ID>
- jail configuration: jail.conf
[tor-netscan]
enabled = true
# below you specify the destination ports to monitor
filter = tor-netscan[DPT=(?:20|21|22|23|389|636|989|990|992)]
banaction = tor-netscan
# and here you specify the thresholds
findtime = 3s
maxretry = 20
bantime = 15m
backend = systemd
journalmatch = _TRANSPORT=kernel
After updating the configuration files, run fail2ban-client reload
to reload them.
Verify that everything has loaded properly by checking the jail status:
fail2ban-client status tor-netscan
# Status for the jail: tor-netscan
# |- Filter
# | |- Currently failed: 4
# | |- Total failed: 556
# | `- Journal matches: _TRANSPORT=kernel
# `- Actions
# |- Currently banned: 1
# |- Total banned: 14
# `- Banned IP list: 22
💡 If you need to set up different thresholds for different ports, simply clone the jail configuration entry, rename it, and adjust it accordingly.
To test whether the ban system is working correctly,
you need to simulate a high volume of connections to a specific IP address and port within a short period of time.
For this example, we'll use IP address 1.1.1.1
and port 22
(thank you, Cloudflare).
- Run the following command in your terminal to repeatedly connect to the target IP and port 30 times:
for i in {1..30}; do (echo >/dev/tcp/1.1.1.1/22 &) done
- If the ban system is working correctly, you should see ufw entries similar to the following:
ufw status
# …
# 22 REJECT OUT Anywhere # (Automatic) fail2ban-tor-netscan : 1682313892
# 22 (v6) REJECT OUT Anywhere (v6) # (Automatic) fail2ban-tor-netscan : 1682313892
# …
These entries indicate that outgoing traffic to port 22 has been temporarily rejected due to the high volume of connection attempts, and the fail2ban system is functioning as intended.
- Now when you attempt to connect once more, you should see the connection being refused:
echo >/dev/tcp/1.1.1.1/22
# bash: connect: Connection refused
# bash: /dev/tcp/1.1.1.1/22: Connection refused
This refusal confirms that the ufw is effectively blocking connections to the specified port. The complete system is functioning as intended.
This solution does not distinguish between repeated connections to the same IP address and unique addresses, which makes it tricky to find the perfect balance in the ban threshold configuration.
Remember not to ban outgoing traffic connections to a port you use yourself. Add whitelisting firewall rules if necessary.
While this solution has its limitations, it helps address the Tor netscan problem and enables you to continue operating a Tor exit node without worrying about receiving netscan abuse notices. The use of fail2ban, despite its performance issues, provides flexibility and easy maintainability for this purpose. Sharing these findings aims to help others facing similar issues, as there were no other solutions available at the time.
tor-netscan-mitigation.md by Kamil Monicz (Zaczero) is licensed under CC BY 4.0