-
Notifications
You must be signed in to change notification settings - Fork 7
/
ipv6-rules.sh
executable file
·280 lines (242 loc) · 7.75 KB
/
ipv6-rules.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/bin/bash
# SPDX-License-Identifier: GPL-3.0-or-later
# set -x
# addCommon() and addTor() implement a DDoS solution for a Tor relay for IPv6
# the remaining code just parses the config and maintains ipset content
# https://github.com/toralf/torutils
function relay_2_ip_and_port() {
if [[ ! $relay =~ '[' || ! $relay =~ ']' || $relay =~ '.' || ! $relay =~ ':' ]]; then
echo " relay '$relay' is invalid" >&2
return 1
fi
read -r orip orport <<<$(sed -e 's,]:, ,' -e 's,\[, ,' <<<$relay)
if [[ $orip == "::" ]]; then
orip+="/0"
echo " notice: using global unicast IPv6 address [::]" >&2
fi
}
function addCommon() {
# allow loopback
$ipt -A INPUT --in-interface lo -m comment --comment "DDoS IPv6 $(date -R)" -j ACCEPT
# IPv6 Multicast
$ipt -A INPUT -p udp --source fe80::/10 --dst ff02::/80 -j ACCEPT
# make sure NEW incoming tcp connections are SYN packets
$ipt -A INPUT -p tcp ! --syn -m state --state NEW -j $jump
$ipt -A INPUT -m conntrack --ctstate INVALID -j $jump
# do not touch established connections
$ipt -A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
# ssh
local addr=$(grep -E "^ListenAddress\s+.*:.*:.*$" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk '{ print $2 }')
local port=$(grep -m 1 -E "^Port\s+[[:digit:]]+$" /etc/ssh/sshd_config /etc/ssh/sshd_config.d/*.conf 2>/dev/null | awk '{ print $2 }')
for i in ${addr:-"::/0"}; do
$ipt -A INPUT -p tcp --dst $i --dport ${port:-22} --syn -j ACCEPT
done
# ratelimit ICMP echo
$ipt -A INPUT -p ipv6-icmp --icmpv6-type echo-request -m limit --limit 6/s -j ACCEPT
$ipt -A INPUT -p ipv6-icmp -j ACCEPT
# DHCPv6
$ipt -A INPUT -p udp --dport 546 -j ACCEPT
}
function addTor() {
__create_ipset $trustlist "maxelem $((2 ** 6))"
__fill_trustlist &
local hashlimit="-m hashlimit --hashlimit-mode srcip,dstport --hashlimit-srcmask $prefix"
for relay in $*; do
relay_2_ip_and_port
local common="$ipt -A INPUT -p tcp --dst $orip --dport $orport"
local ddoslist="tor-ddos6-$orport" # this holds ips classified as DDoS'ing the local OR port
__create_ipset $ddoslist "maxelem $max timeout $((24 * 3600)) netmask $prefix"
__fill_ddoslist &
# rule 1
local trust_rule="INPUT -p tcp --dst $orip --syn -m set --match-set $trustlist src -j ACCEPT"
if ! $ipt -C $trust_rule 2>/dev/null; then
$ipt -A $trust_rule
fi
# rule 2
$common $hashlimit --hashlimit-name tor-ddos-$orport --hashlimit-above 9/minute --hashlimit-burst 1 --hashlimit-htable-expire $((2 * 60 * 1000)) -j SET --add-set $ddoslist src --exist
$common -m set --match-set $ddoslist src -j $jump
# rule 3
$common -m connlimit --connlimit-mask $prefix --connlimit-above 9 -j $jump
# rule 4
$common --syn -j ACCEPT
done
}
function __create_ipset() {
local name=$1
local cmd="ipset create -exist $name hash:ip family inet6 ${2-}"
if ! $cmd 2>/dev/null; then
if ! saveIpset $name && ipset destroy $name && $cmd; then
return 1
fi
fi
}
function __fill_trustlist() {
# this is intentionally not filled from a saved set at reboot
(
# snowflakes
echo 2607:f018:600:8:be30:5bff:fef1:c6fa 2a0c:dd40:1:b::42
# Tor authorities
echo 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
getent ahostsv6 snowflake-01.torproject.net. snowflake-02.torproject.net. | awk '{ print $1 }' | sort -u
if relays=$(curl -s 'https://onionoo.torproject.org/summary?search=flag:authority' -o -); then
if [[ $relays =~ 'relays_published' ]]; then
jq -r '.relays[] | .a | select(length > 1) | .[1:]' <<<$relays |
tr ',' '\n' | grep -F ':' | tr -d ']["' |
sort
fi
fi
) |
xargs -r -n 1 -P $jobs ipset add -exist $trustlist
}
function __fill_ddoslist() {
if [[ -s $tmpdir/$ddoslist ]]; then
ipset flush $ddoslist
xargs -r -L 1 -P $jobs ipset add -exist $ddoslist <$tmpdir/$ddoslist # -L 1 b/c the inputs are tuples
fi
rm -f $tmpdir/$ddoslist
}
function additionalServices() {
# local-address:local-port
for service in ${ADD_LOCAL_SERVICES6-}; do
read -r addr port <<<$(sed -e 's,]:, ,' -e 's,\[, ,' <<<$service)
if [[ $addr == "::" ]]; then
addr+="/0"
fi
$ipt -A INPUT -p tcp --dst $addr --dport $port --syn -j ACCEPT
done
# remote-address>local-port
for service in ${ADD_REMOTE_SERVICES6-}; do
read -r addr port <<<$(sed -e 's,]>, ,' -e 's,\[, ,' <<<$service)
if [[ $addr == "::" ]]; then
addr+="/0"
fi
$ipt -A INPUT -p tcp --src $addr --dport $port --syn -j ACCEPT
done
}
function addHetzner() {
local sysmon="hetzner-sysmon6"
__create_ipset $sysmon
{
(
echo 2a01:4f8:0:a101::5:1 2a01:4f8:0:a101::6:1 2a01:4f8:0:a101::6:2 2a01:4f8:0:a101::6:3 2a01:4f8:0:a112::c:1
getent ahostsv6 pool.sysmon.hetzner.com | awk '{ print $1 }' | sort -u
) |
xargs -r -n 1 -P $jobs ipset add -exist $sysmon
} &
$ipt -A INPUT -m set --match-set $sysmon src -j ACCEPT
}
function clearRules() {
$ipt -P INPUT ACCEPT
$ipt -F
$ipt -X
$ipt -Z
}
function printRuleStatistics() {
date -R
echo
$ipt -nv -L INPUT $*
}
# OR port + address are defined in 1 line
function getConfiguredRelays6() {
grep -h -e "^ORPort *" /etc/tor/torrc* /etc/tor/instances/*/torrc 2>/dev/null |
grep -v -F -e ' NoListen' -e ':auto' |
grep -P "^ORPort\s+\[[0-9a-f]*:[0-9a-f:]*:[0-9a-f]*\]:\d+\s*" |
awk '{ print $2 }'
}
function bailOut() {
local rc=$?
if [[ $rc -gt 128 ]]; then
local signal=$((rc - 128))
if [[ $signal -eq 13 ]]; then # PIPE
return 0
fi
fi
trap - INT QUIT TERM EXIT
echo -e "\n Something went wrong, stopping ...\n" >&2
clearRules
exit $rc
}
function saveIpset() {
local name=$1
local tmpfile=$(mktemp /tmp/$(basename $0)_XXXXXX.tmp)
if ipset list $name | sed -e '1,8d' >$tmpfile; then
if [[ -s $tmpfile ]]; then
cp $tmpfile $tmpdir/$name
fi
fi
rm $tmpfile
}
function saveCertainIpsets() {
ipset list -n | grep -e '^tor-ddos6-[0-9]*$' -e '^tor-trust6$' |
while read -r name; do
saveIpset $name
done
}
#######################################################################
set -eu
export LANG=C.utf8
export PATH=/usr/sbin:/usr/bin:/sbin/:/bin
umask 066
# check if regular iptables works or if the legacy variant is explicitly needed
ipt="ip6tables"
set +e
$ipt -nv -L INPUT &>/dev/null
rc=$?
if [[ $rc -ne 0 ]]; then
if [[ $rc -eq 4 ]]; then
ipt+="-legacy"
fi
$ipt -nv -L INPUT &>/dev/null
rc=$?
if [[ $rc -ne 0 ]]; then
echo " $ipt is not working as expected" >&2
exit 1
fi
fi
set -e
trustlist="tor-trust6" # Tor authorities and snowflake servers
jobs=$((1 + $(nproc) / 2)) # parallel jobs of adding ips to an ipset
prefix=80 # any ipv6 address of this CIDR block is considered to belong to the same source/owner
# hash and ipset size: 1M if > 32 GiB, 256K if > 2 GiB, default: 64K
ram=$(awk '/MemTotal/ { print int ($2 / 1024 / 1024) }' /proc/meminfo)
if [[ ${ram} -gt 32 ]]; then
max=$((2 ** 20))
elif [[ ${ram} -gt 2 ]]; then
max=$((2 ** 18))
else
max=$((2 ** 16))
fi
tmpdir=${TORUTILS_TMPDIR:-/var/tmp}
jump=${RUN_ME_WITH_SAFE_JUMP_TARGET:-DROP}
action=${1-}
[[ $# -gt 0 ]] && shift
case $action in
start)
trap bailOut INT QUIT TERM EXIT
clearRules
addCommon
addHetzner
additionalServices
addTor ${*:-${CONFIGURED_RELAYS6:-$(getConfiguredRelays6)}}
$ipt -P INPUT ${RUN_ME_WITH_SAFE_JUMP_TARGET:-$jump}
trap - INT QUIT TERM EXIT
;;
stop)
saveCertainIpsets
clearRules
;;
update)
__fill_trustlist
;;
test)
export RUN_ME_WITH_SAFE_JUMP_TARGET="ACCEPT"
type ipset iptables jq
$0 start $*
;;
save)
tmpdir=${1:-$tmpdir} saveCertainIpsets
;;
*)
printRuleStatistics $*
;;
esac