Few tools for a Tor relay.
The scripts ipv4-rules.sh and ipv6-rules.sh protect a Tor relay against DDoS attacks¹ at the IP network layer, as seen in this metrics:
An older example is here.
¹ see ticket 40636 and 40093 of the Tor project.
Mark an IP address as malicious if its connection attempts over a short time interval exceed a given threshold. Then block that IP for a long time interval.
Therefore a simple network rule won't make it. However ipset helps to achieve the goal. Further considerations:
- never touch established connections
- avoid to not overblock
Install jq, ipset and iptables, e.g. for Debian or Ubuntu do:
sudo apt update
sudo apt install -y jq ipset iptablesDownload the DDoS prevention scripts
wget -q https://raw.githubusercontent.com/toralf/torutils/main/ipv4-rules.sh -O ipv4-rules.sh
wget -q https://raw.githubusercontent.com/toralf/torutils/main/ipv6-rules.sh -O ipv6-rules.sh
chmod +x ./ipv4-rules.sh ./ipv6-rules.shMake a backup of the current iptables filter table:
sudo /usr/sbin/iptables-save >./rules.v4
sudo /usr/sbin/ip6tables-save >./rules.v6Run a quick test
sudo ./ipv4-rules.sh test
sudo ./ipv6-rules.sh testBest is to stop the Tor service(s) now. Then flush the connection tracking table
sudo /usr/sbin/conntrack -Fand (re-)start the Tor service. Check that your ssh login and your other services are still working. Watch the iptables live statistics by:
sudo watch -t ./ipv4-rules.sh # replace 4 with 6 for IPv6If something failed then restore the backuped state:
sudo ./ipv4-rules.sh stop
sudo ./ipv6-rules.sh stop
sudo /usr/sbin/iptables-restore <./rules.v4
sudo /usr/sbin/ip6tables-restore <./rules.v6But if everything looks ok, then run the DDoS script with the parameter start:
sudo ./ipv4-rules.sh start
sudo ./ipv6-rules.sh startEnsure that the package iptables-persistent is either de-installed or at least disabled. Create cron jobs like:
# DDoS prevention
@reboot /root/ipv4-rules.sh start; /root/ipv6-rules.sh start
# have recent ipset data after a reboot available
@hourly /root/ipv4-rules.sh save; /root/ipv6-rules.sh save
# update Tor authorities
@daily /root/ipv4-rules.sh update; /root/ipv6-rules.sh update
# DDoS for /64 IPv6 hostmask
*/5 * * * * /opt/torutils/ipv6-rules.sh manualI appreciate reports about any findings via the issue tracker.
The DDoS script creates generic filter rules for the local network, ICMP, ssh, DHCP and additional services (if given).
Then this rule set is applied to prevent DDoS attampts against the Tor port:
- trust any connection attempt from a Tor authority node
- block the source¹ for 24 hours if the connection attempt rate from it to the Tor port exceeds 9/min² within last 2 minutes
- ignore the connection attempt if there are already 8 established connections to the Tor port (max 8 relays are allowed per ip address)
- accept the connection attempt to the Tor port
¹ For IPv4 the source is a single ip address, for IPv6 source is a netmask of either /64, /80 or /128, see implementation details here.
² The value is derived from ticket 40636.
If the DDoS script fails to parse the Tor and/or the SSH config then overrule automatic parsing by either:
-
define the local running relay/s explicitly at the command line after the keyword
start, e.g.:sudo ./ipv4-rules.sh start 1.2.3.4:443 5.6.7.8:9001
-
-or- define them as environment variables, e.g.:
sudo CONFIGURED_RELAYS="5.6.7.8:9001 1.2.3.4:443" ./ipv4-rules.sh start(
CONFIGURED_RELAYS6for IPv6).
A command line argument takes precedence over the corresponding environment variable.
Allow inbound traffic to additional <address:port> destinations by e.g.:
export ADD_LOCAL_SERVICES="2.71.82.81:828 3.141.59.26:53"
export ADD_LOCAL_SERVICES6="[cafe::abba]:1234"A slightly different syntax is used for ADD_REMOTE_SERVICES to allow inbound traffic, e.g.:
export ADD_LOCAL_SERVICES="4.3.2.1>4711"
export ADD_LOCAL_SERVICES6="[cafe::abba]>4711"(the separator > is used instead : to highlight that the address is src, and the port is dst)
The above allows traffic from the specified remote address to the local port 4711.
Please note: The script sets few sysctl values. If unwanted then comment out the code around setSysctlValues(). If Hetzners system monitor isn't used, then comment out addHetzner() too. To append rules onto an existing iptables rule set (overwrite is the default) comment out the call clearRules().
The script metrics.sh exports DDoS metrics into a Prometheus readable file. More details plus few Grafana dashboards are here.
Graphs¹ of rx/tx packets, traffic and socket counts from 5th, 6th and 7th of Nov show the results for few DDoS attacks over 3 days for 2 relays. A more heavier attack was observed at 12th of Nov. A periodic drop down of the socket count metric, vanishing over time, appeared at 5th of Dec. Current attacks e.g. at the 7th of March are still handled well. Few more helper scripts were developed to analyze the attack vector. Look here for details.
¹ using sysstat, created e.g. by
# create the SVG file
svg=/tmp/graph.svg
sadf -g -t /var/log/sa/sa${DAY:-`date +%d`} -O skipempty,oneday -- -n DEV,SOCK,SOCK6 --iface=enp8s0 >$svg
# fix SVG canvas size
h=$(tail -n 2 $svg | head -n 1 | cut -f 5 -d ' ')
sed -i -e "s,height=\"[0-9]*\",height=\"$h\"," $svg
# display it
firefox $svgI used this Ansible code to deploy and configure Tor relays and Snowflake standalone proxies.
Every then and when I get an undesired abuse complaint from my hoster. To avoid this I developed ipv4-rules-egress.sh. Details are tracked in this ticket.
info.py gives a summary of all Tor related TCP connections, e.g.:
sudo ./info.py --address 127.0.0.1 --ctrlport 9051
ORport 9051 0.4.8.0-alpha-dev uptime: 02:58:04 flags: Fast, Guard, Running, Stable, V2Dir, Valid
+------------------------------+-------+-------+
| Type | IPv4 | IPv6 |
+------------------------------+-------+-------+
| Inbound to our OR from relay | 2304 | 885 |
| Inbound to our OR from other | 3188 | 68 |
| Inbound to our ControlPort | | 1 |
| Outbound to relay OR | 2551 | 629 |
| Outbound to relay non-OR | | |
| Outbound exit traffic | | |
| Outbound unknown | 16 | 4 |
+------------------------------+-------+-------+
| Total | 8059 | 1587 |
+------------------------------+-------+-------+
relay OR connections 6369
relay OR ips 5753
3 inbound v4 with > 2 connections eachIf your Tor relay is an Exit then ps.py gives live statistics about those network connections:
sudo ./ps.py --address 127.0.0.1 --ctrlport 9051orstatus.py prints the closing reason to stdout, orstatus-stats.sh prints/plots statistics (see this example) from that.
orstatus.py --ctrlport 9051 --address 127.0.0.1 >>/tmp/orstatus &
sleep 3600
orstatus-stats.sh /tmp/orstatuskey-expires.py helps to maintain Tor offline keys. It returns the expiration time in seconds of the mid-term signing key. A cronjob for that task could look like this:
seconds=$(sudo ./key-expires.py /var/lib/tor/keys/ed25519_signing_cert)
days=$((seconds / 86400))
[[ $days -lt 23 ]] && echo "Tor signing key expires in less than $days day(s)"If Tor metrics are enabled then this 1-liner does a similar job (replace 9052 with the metrics port):
date -d@$(curl -s localhost:9052/metrics | grep "^tor_relay_signing_cert_expiry_timestamp" | awk '{ print $2 }')An open Tor control port is needed to query the Tor process via API. Configure it in torrc, e.g.:
ControlPort 127.0.0.1:9051The python library Stem is needed. Install it either by your package manager, e.g. for Ubuntu:
sudo apt install python3-stem-or- (better) use the more recent git sources, e.g.:
git clone https://github.com/torproject/stem.git
export PYTHONPATH=$PWD/stemThe script watch.sh helps to constantly monitor the host and Tor log files. It sends findings via mailx.
log=/tmp/${0##*/}.log
# watch syslog
/opt/torutils/watch.sh /var/log/messages /opt/torutils/watch-messages.txt &>>$log &
# watch Tor
/opt/torutils/watch.sh /var/log/tor/notice.log /opt/torutils/watch-tor.txt -v &>>$log &