Alright folks I am trying to employ better rate limiting on my DNS server. Now ive found quite a few things on the internet and amalgamated them together but Im having difficulty understanding them. Can someone walk me through this a bit more?
:PREROUTING ACCEPT [0:0]
:OUTPUT ACCEPT [0:0]
:DNS_DROP - [0:0]
:DYN_DROP - [0:0]
-A PREROUTING -i lo -j ACCEPT
-A PREROUTING -i eth0 -s <our.net.work.space>/21 -j ACCEPT
-A PREROUTING -m recent --rsource --update --seconds 86400 --name DYN_DROP_LIST -j DROP
-A PREROUTING -p udp -m udp --dport 53 -j DNS_DROP
-A PREROUTING -p tcp -m tcp --dport 53 -j DNS_DROP
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -o eth0 -d <our.net.work.space>/21 -j ACCEPT
-A OUTPUT -m recent --rdest --update --seconds 86400 --name DYN_DROP_LIST -j DROP
## Permanent DNS drop list (ipset)
-A DNS_DROP -m set --match-set PERM_DNS_DROP src -j DROP
## DNS - UDP progressive rate limit
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 5 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 10000 --hashlimit-name DNS_LIMIT_UDP -j DROP
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 10/min --hashlimit-burst 10 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 120000 --hashlimit-name DNS_LIMIT_UDP2 -j DROP
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 4/min --hashlimit-burst 15 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 240000 --hashlimit-name DNS_LIMIT_UDP3 -j DROP
## DNS - TCP progressive rate limit
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 6/sec --hashlimit-burst 30 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 10000 --hashlimit-name DNS_LIMIT_TCP -j DROP
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 60/min --hashlimit-burst 60 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 120000 --hashlimit-name DNS_LIMIT_TCP2 -j DROP
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 24/min --hashlimit-burst 90 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 240000 --hashlimit-name DNS_LIMIT_TCP3 -j DROP
## DNS - UDP & TCP root any/all query filter
-A DNS_DROP -p udp -m string --hex-string "|0000ff0001|" --algo bm --from 40 -j DYN_DROP
-A DNS_DROP -p tcp -m string --hex-string "|0000ff0001|" --algo bm --from 52 -j DYN_DROP
## Dynamic Drop List
-A DYN_DROP -m recent --rsource --set --name DYN_DROP_LIST -j DROP
-A PREROUTING -i lo -j ACCEPT
-A PREROUTING -i eth0 -s <our.net.work.space>/21 -j ACCEPT
-A PREROUTING -m recent --rsource --update --seconds 86400 --name DYN_DROP_LIST -j DROP
-A PREROUTING -p udp -m udp --dport 53 -j DNS_DROP
-A PREROUTING -p tcp -m tcp --dport 53 -j DNS_DROP
-A OUTPUT -o lo -j ACCEPT
-A OUTPUT -o eth0 -d <our.net.work.space>/21 -j ACCEPT
-A OUTPUT -m recent --rdest --update --seconds 86400 --name DYN_DROP_LIST -j DROP
The paste I found was just a paste. No explanation. You are expected to figure it out. If someone can help me decipher this I can discuss it in detail on my DNS rate limit and amplification attack prevention part of my posts on making your own DNS server but I gotta understand this better before I do
This graphic may help you visually see the flow of packets through iptables/netfilter. The bottommost layer in the graphic (the link layer) probably isn’t relevant for you currently if you’re not doing any interface bridging, so just look at the flow through the network layer and higher:
These are your chains and their default policies. Packets in your PREROUTING chain and OUTPUT chains will be accepted and allowed to pass through if they don’t match any rules.
Accepts incoming traffic from localhost or from your LAN without further analysis.
If traffic has been identified by your separate chain where you did all the hash ratelimiting checks and gets here:
(assuming the packet contents matched that hex pattern) it gets tagged with that DYN_DROP_LIST name, and that recent rule detects it here and drops it before it ever gets routed (since it’s in the PREROUTING chain).
If we get this far, send in DNS traffic on port 53 into your DNS_DROP chain where it get get tested for the ratelimits you have set up, and into the section I quoted above.
For outgoing traffic leaving your host, accept all traffic destined for localhost or for your LAN.
Same as above, drop outgoing traffic tagged with that DYN_DROP_LIST name.
I’m not 100% certain, but this looks like it’s filtering all your DDOS traffic that exceeds your defined ratelimit. It’s not getting the DYN_DROP_LIST flag set because it probably already did before iptables recognized it exceeding your limits.
Yes I believe that would work. In case I was a bit unclear earlier, a hypothetical bad packet received for the first time would make it all the way through the DNS_DROP chain and then jump to the DYN_DROP chain to get dropped via here:
Then the second time a packet comes in (within 86400 seconds) matching that source IP it gets dropped here:
The only other hitch I can think of is if you have default DROP policies on your PREROUTING or OUTPUT chains. If you do, you’ll need a rule in there somewhere allowing DNS traffic (-j ACCEPT), but you likely already have this in place if that’s true.
The bottom link layer isn’t applicable here unless you’re doing interface bridging. The prerouting chain is running before it gets to your red box above. You’ll need to also make sure your INPUT and OUTPUT chains are allowing through all port 53 traffic by one of the following ways:
Your INPUT chain has a default policy of ACCEPT and you don’t have any rules blocking port 53 traffic
Your INPUT chain has a default policy of DROP and you do have explicit rules for allowing port 53 traffic.
It should be ok to allow at this point because once it’s made it past prerouting, you know it’s good and legitimate incoming DNS traffic.
Same goes for your OUTPUT chain, so responses get back out to the internet.
I think the :DNS_DROP and :DYN_DROP should be right below *filter with all the other rule chains, and the rules on those chains also in the filter section.
That’s really nice! Depending on how many different IPs they’re spamming you from, it might not be a bad idea to lengthen the 86400 seconds (1 day) timeout period if they just keep slamming you immediately after they get unbanned 1 day later.
good point. I was thinking of modifying the hash rates too
On average I get 2500 queries an hour. This is about the peak actually. SO what should I make these to say limit them more without hurting existing users?
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 1/sec --hashlimit-burst 3 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 10000 --hashlimit-name DNS_LIMIT_UDP -j DROP
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 10/min --hashlimit-burst 9 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 120000 --hashlimit-name DNS_LIMIT_UDP2 -j DROP
-A DNS_DROP -p udp -m hashlimit --hashlimit-above 4/min --hashlimit-burst 9 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 240000 --hashlimit-name DNS_LIMIT_UDP3 -j DROP
## DNS - TCP progressive rate limit
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 6/sec --hashlimit-burst 30 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 10000 --hashlimit-name DNS_LIMIT_TCP -j DROP
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 60/min --hashlimit-burst 60 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 120000 --hashlimit-name DNS_LIMIT_TCP2 -j DROP
-A DNS_DROP -p tcp -m hashlimit --hashlimit-above 24/min --hashlimit-burst 90 --hashlimit-mode srcip --hashlimit-srcmask 24 --hashlimit-htable-expire 240000 --hashlimit-name DNS_LIMIT_TCP3 -j DROP
It’s hard to say exactly - I’d probably wait until tomorrow at the earliest and possibly even a week from now to analyze your traffic and see how much you’re blocking vs. permitting, and make these sorts of decisions when you have more knowledge of your baseline data. I’m only an amateur network admin with a home server and segregated VLANs for guests and IoT devices, and others who do this stuff professionally may have more/better input here.