I implemented this easily some time ago due to a situation where product
development was unable or unwilling to disable open resolvers.
i'll post my ruleset then describe it then describe it since it contains
multiple functions.
Chain INPUT (policy ACCEPT 68M packets, 4377M bytes)
pkts bytes target prot opt in out
source destination
22M 1423M ACCEPT all -- lo *
0.0.0.0/0 0.0.0.0/0
0 0 REJECT all -- * *
0.0.0.0/0 0.0.0.0/0 recent: CHECK name:
blacklist side: source reject-with icmp-admin-prohibited
34M 2463M find_dnsany udp -- * *
0.0.0.0/0 0.0.0.0/0 udp dpt:53
Chain FORWARD (policy ACCEPT 460M packets, 298G bytes)
pkts bytes target prot opt in out
source destination
0 0 REJECT all -- * *
0.0.0.0/0 0.0.0.0/0 recent: CHECK name:
blacklist side: source reject-with icmp-admin-prohibited
0 0 irc tcp -- * eth0
0.0.0.0/0 0.0.0.0/0 multiport dports
6660:6669,6670
1826M 1144G local_ips all -- * *
0.0.0.0/0 0.0.0.0/0
35387 2569K find_dnsany udp -- * *
0.0.0.0/0 0.0.0.0/0 udp dpt:53
Chain OUTPUT (policy ACCEPT 39M packets, 316G bytes)
pkts bytes target prot opt in out
source destination
0 0 irc tcp -- * eth0
0.0.0.0/0 0.0.0.0/0 multiport dports
6660:6669,6670
22M 1423M ACCEPT all -- * lo
0.0.0.0/0 0.0.0.0/0
310M 1637G local_ips all -- * *
0.0.0.0/0 0.0.0.0/0
13M 1056M CONNMARK udp -- * *
0.0.0.0/0 0.0.0.0/0 udp dpt:53 owner UID match
25 CONNMARK set 0x35
13M 1056M find_dnsany udp -- * *
0.0.0.0/0 0.0.0.0/0 udp dpt:53
Chain find_dnsany (3 references)
pkts bytes target prot opt in out
source destination
302K 19M limit_dnsany all -- * *
0.0.0.0/0 0.0.0.0/0 u32
"0x0>>0x16&0x3c@0x8>>0xf&0x1=0x0&&0x0>>0x18&0x1=0x1" STRING match
"|0000ff0001|" ALGO name bm FROM 36 TO 70 /* match ANY? queries */
Chain irc (2 references)
pkts bytes target prot opt in out
source destination
0 0 ULOG all -- * *
0.0.0.0/0 0.0.0.0/0 ULOG copy_range 0 nlgroup
30 queue_threshold 1
0 0 LOG all -- * *
0.0.0.0/0 0.0.0.0/0 LOG flags 8 level 4 prefix
"[IRC] "
0 0 REJECT all -- * *
0.0.0.0/0 0.0.0.0/0 reject-with
icmp-admin-prohibited
Chain limit_dnsany (1 references)
pkts bytes target prot opt in out
source destination
827 53727 ACCEPT all -- * * 1.2.3.4
0.0.0.0/0 limit: avg 20/min burst 60
0 0 limit_venet all -- * * 1.2.3.4
0.0.0.0/0
4297 302K ACCEPT all -- * *
0.0.0.0/0 0.0.0.0/0 CONNMARK match 0x35
limit: avg 10/min burst 30
22798 1475K ACCEPT all -- * *
0.0.0.0/0 0.0.0.0/0 limit: avg 4/min burst 10
7277 468K LOG all -- * *
0.0.0.0/0 0.0.0.0/0 limit: avg 1/min burst 5
LOG flags 0 level 4 prefix "DNSANY: "
279K 18M DROP all -- * *
0.0.0.0/0 0.0.0.0/0
Chain limit_venet (1 references)
pkts bytes target prot opt in out
source destination
0 0 LOG all -- * *
0.0.0.0/0 0.0.0.0/0 limit: avg 1/min burst 5
LOG flags 0 level 4 prefix "DNSANYint: "
0 0 REJECT all -- * *
0.0.0.0/0 0.0.0.0/0 reject-with
icmp-admin-prohibited
Chain local_ips (2 references)
pkts bytes target prot opt in out
source destination
2136M 2782G RETURN all -- * !eth0
0.0.0.0/0 0.0.0.0/0 /* only check outgoing
packets */
0 0 RETURN all -- * *
0.0.0.0/0 0.0.0.0/0 ADDRTYPE match src-type
LOCAL /* accept packet generated from any locally bound IP */
0 0 RETURN all -- * *
0.0.0.0/0 0.0.0.0/0 recent: CHECK name:
local_ips side: source
0 0 ULOG all -- * *
0.0.0.0/0 0.0.0.0/0 ULOG copy_range 0 nlgroup
30 queue_threshold 1
0 0 LOG all -- * *
0.0.0.0/0 0.0.0.0/0 limit: avg 1/min burst 5
/* block non-local IPs from exiting */ LOG flags 8 level 4 prefix
"SPOOF: "
0 0 REJECT all -- * *
0.0.0.0/0 0.0.0.0/0 reject-with
icmp-admin-prohibited
First, INPUT, FORWARD, and OUTPUT are very similar.
1) INPUT accepts all /lo/ traffic as does OUTPUT
2) INPUT and FORWARD auto block any IPs admins put into the
//proc/net/ipt_recent/blacklist/ (or //proc/net/xt_recent/blacklist/
depending on kernel version)
3) common IRC port traffic is blocked (insane number of bots use these
ports)
4) outgoing IPs are matched to interface IPs. this set of rules are used
for when the kernel is too old to employ
//proc/sys/net/ipv4/conf/all/rp_filter/ mode against forwarded IPs too
(prevents spoofing)
5) OUTPUT tags DNS traffic owned by uid 25 (usually for sendmail,
postfix, etc, change accordingly)
6) in order to prevent killing our box with syslog when an attack
happens, logging is strictly rate limited
now on to the DNS specific stuff
1) each of INPUT, FORWARD, and OUTPUT call /find_dnsany/ to identify our
suspect traffic
2) the rule in /find_dnsany/ uses a u32 match rule to first identify DNS
traffic that is a query, and a string match to identify the ANY flag,
further, we try to make this efficient by limiting our match to the byte
range of 36 to 70. we've found that 100% of our DNS amplification
attacks request zone data that fits within this. it certainly may be a
longer zone but is unlikely. change accordingly
3) once DNS ANY queries have been identified, jump to /limit_dnsany/
4) /limit_dnsany/ is designed to accept packets until a limit is
reached. there are three limits employed:
4.1) customers in a VZ or bound to a specific local IP that may have
higher than normal rates of legitimate DNS ANY queries
4.2) our local smtp initiated lookups. qmail is horrible in that it
employs DNS ANY (broken design, discussion out of scope)
4.3) all other DNS ANY traffic
5) incoming DNS ANY that is rate limited is DROPped on the floor, rate
limited outgoing is REJECted so internal customers get a friendly icmp
reject message
additional notes:
1) outgoing should also be checked against the blacklist, somehow our QA
dropped that rule before disting
2) IRC is checked before local traffic to ensure two infected customers
aren't communicating with each other
this set of rules reliably filters:
1) incoming and outgoing DNS QUERY floods. incoming is rate limited to
4/min per IP source. forwarded and outgoing has higher limits
2) majority of bot traffic on IRC ports
3) spoofed and blacklisted packets
-david