Syn flood protection for FORWARD?

#!/bin/sh

#config Section
wan_device="" #setting a device between the quotation marks disable auto detection, "" autotection
	          #you can set more then one Wan interface with a comma between the device names for example "eth0,eth1"

bogon="0" #enable Bogon filter "1" enable "0" disable

forward_router="0.0.0.0" #Enter here the Ip address/network of the upstream router if you use the Bogon filter and have a forward router.
			                 #You can add multiple ip addresses or networks with a comma betwen the addresses, network format at example 192.168.0.0/24
syn_flood="0"  #enable syn flood protection

icmp_flood="0" #enable icmp flood protection

udp_flood="0"  #enable udp flood protection

port_scan_detection="0" #enable Portscan detection

arp_limit_enable="0" #enable ARP limit "1" enable "0" disable

wan_input_drop_enable="0"       #Drops inet input to wan interface

wireguard_input_drop_enable="0" #Drops inet input to wireguard interface

reject_with_icmp="0"		#Reject Wan/Wireguard input with Icmp unreachable 

#Parameters

arp_limit="1"	#accepted ARP request per second and on-the-fly per MAC address

syn_flood_limit="25" 	   #syn flood limit

syn_flood_burst_limit="50" #indicates the number of packets that can exceed the rate limit, must be greater than or equal to 1

icmp_flood_limit="15"      #icmp flood limit

icmp_flood_burst_limit="5" #indicates the number of packets that can exceed the rate limit, must be greater than or equal to 1

udp_flood_limit="100"       #udp flood limit

udp_flood_burst_limit="50" #indicates the number of packets that can exceed the rate limit, must be greater than or equal to 1

portscan_limit="50"         #sets the packet limit before the address is blocked

portscan_drop_time="5m"   #Sets the time limit in which the source of the port scan is blocked s=seconds m=minutes h=hours

portscan_src_ports="22" #remote ports for which portscan does not respond  

portscan_dst_ports="22" #target ports for which portscan does not respond

bogon_adresses="0.0.0.0/8, \
		10.0.0.0/8, \
		100.64.0.0/10, \
		127.0.0.0/8, \
		169.254.0.0/16, \
		172.16.0.0/12, \
		192.0.0.0/24, \
		192.0.2.0/24, \
		192.168.0.0/16, \
		198.18.0.0/15, \
		198.51.100.0/24, \
		203.0.113.0/24, \
		224.0.0.0/4, \
		240.0.0.0/4, \
		255.255.255.255/32"

bogon_ipv6_adresses="::/128, \
		     ::1/128, \
		     ::ffff:0:0/96, \
		     ::/96, \
		     100::/64, \
		     2001:10::/28, \
		     2001:db8::/32, \
		     fc00::/7, \
		     fe80::/10, \
		     fec0::/10, \
		     ff00::/8"

#config Section

verbose=false

if [ -z "$wan_device" ]; then

wan_device=$(uci get network.wan.device)

fi

nft list ruleset | grep -q 'netdev' && nft delete table netdev NETDEV && nft delete table inet DDOS
nft list ruleset | grep -q 'block_tcp_portscan' && nft delete table netdev block_tcp_portscan && nft delete table inet tcp_portscan
nft list ruleset | grep -q 'ARP' && nft delete table arp ARP

if [ $arp_limit_enable -ge 1 ]; then

nft -f - <<TABLE
table arp ARP {
	chain Arp_limit { type filter hook input priority 0; policy accept;
	arp operation 1 meter per-mac { ether saddr limit rate $arp_limit/second burst 2 packets } counter accept
	arp operation 1 counter drop
	}
}
TABLE
fi


if [ $port_scan_detection -ge 1 ]; then
nft -f - <<TABLE
table netdev block_tcp_portscan {
        set enemies4 {
                type ipv4_addr
                flags dynamic,timeout
                timeout $portscan_drop_time
        }

        set enemies6 {
                type ipv6_addr
                flags dynamic,timeout
                timeout $portscan_drop_time
    }

    chain portscan_filter {
        type filter hook ingress devices = { $wan_device } priority -500;
	ip  saddr @enemies4  update @enemies4 { ip  saddr }  counter  drop  comment "Already known-bad, count it and drop"
        ip6 saddr @enemies6  update @enemies6 { ip6 saddr }  counter  drop  comment "Already known-bad, count it and drop"
meta pkttype unicast tcp flags fin,psh,urg / fin,psh,urg jump input_limit
meta pkttype unicast tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 jump input_limit

	}

     chain input_limit {
                limit rate $portscan_limit/second  counter  return
       	        update @enemies4 { ip  saddr } counter drop
                update @enemies6 { ip6 saddr } counter drop
     }
}
TABLE

nft -f - <<TABLE
table inet tcp_portscan {
          set enemies4 {
                  type ipv4_addr
                  flags dynamic,timeout
                 timeout $portscan_drop_time
         }

         set enemies6 {
                 type ipv6_addr
                 flags dynamic,timeout
                 timeout $portscan_drop_time

	}

    chain portscan_prerouting {

    ip  saddr @enemies4  update @enemies4 { ip  saddr }  counter  drop
    ip6 saddr @enemies6  update @enemies6 { ip6 saddr }  counter  drop
    type filter hook prerouting priority -160;
    iifname { $wan_device } ct state established,related counter accept
    iifname { $wan_device } tcp sport != { $portscan_src_ports } tcp flags syn,fin,ack th dport != { $portscan_dst_ports } jump input_limit

     }

    chain input_limit {
                 limit rate $portscan_limit/second  counter  return
                 update @enemies4 { ip  saddr } counter drop
                 update @enemies6 { ip6 saddr } counter drop

	}
}
TABLE
fi

if [[ $reject_with_icmp -ge 1 ]]; then
nft -f - <<TABLE
table inet DDOS {
	chain reject_drop {
	counter reject with icmp type port-unreachable
	counter reject with icmpv6 type port-unreachable
	counter drop
        }
    }
TABLE
else
nft -f - <<TABLE
table inet DDOS {
         chain reject_drop {
	 counter drop
         }
     }
TABLE
fi;

if [[ $syn_flood -ge 1 ]]; then
nft -f - <<TABLE
                 table netdev NETDEV {
		chain syn_flood {
		limit rate $syn_flood_limit/second burst $syn_flood_burst_limit packets return comment "Accept SYN packets below rate-limit"
		counter drop comment "Drop excess packets"
		}
}
TABLE
else
nft -f - <<TABLE
                  table netdev NETDEV {
                 chain syn_flood {
		return
		}
}
TABLE
fi;

if [[ $icmp_flood -ge 1 ]]; then
nft -f - <<TABLE
                 table netdev NETDEV {
		chain icmp_flood {
		limit rate $icmp_flood_limit/second burst $icmp_flood_burst_limit packets return
		counter drop comment "Drop excess packets"
	}
}
TABLE
else
nft -f - <<TABLE
                  table netdev NETDEV {
                 chain icmp_flood {
		 return
	}
}
TABLE
fi;

if [[ $udp_flood -ge 1 ]]; then
nft -f - <<TABLE
                 table inet DDOS {
		chain udp_flood {
		limit rate $udp_flood_limit/second burst $udp_flood_burst_limit packets return
		counter drop comment "Drop excess packets"
		}
}
TABLE
else
nft -f - <<TABLE
                  table inet DDOS {
                 chain udp_flood {
		return
		}
}
TABLE
fi;


nft -f - <<TABLE

		table netdev NETDEV {

  	  chain ingress {
        	type filter hook ingress devices = { $wan_device } priority -495;

		tcp flags syn / fin,syn,rst,ack jump syn_flood comment "!fw4: Rate limit TCP syn packets"

		ip protocol icmp icmp type {echo-reply, destination-unreachable, source-quench, redirect, echo-request, time-exceeded, parameter-problem, timestamp-request, timestamp-reply, info-request, info-reply, \
		 address-mask-request, address-mask-reply, router-advertisement, router-solicitation} jump icmp_flood

		ip protocol icmpv6 icmpv6 type {destination-unreachable, packet-too-big, time-exceeded, echo-request, echo-reply, mld-listener-query, mld-listener-report, mld-listener-reduction, nd-router-solicit, \
		 nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert, nd-redirect, parameter-problem, router-renumbering} jump icmp_flood

		tcp flags & (syn|rst) == (syn|rst) counter drop
		tcp flags syn,psh / syn,psh counter drop
		tcp flags ack,psh,rst / ack,psh,rst counter drop
		tcp flags ack,psh,rst,fin / ack,psh,rst,fin counter drop
		tcp flags ack,psh,rst,syn / ack,psh,rst,syn counter drop
		tcp flags ack,psh,rst,syn,fin / ack,psh,rst,syn,fin counter drop
		tcp flags ack,psh,syn / ack,psh,syn counter drop
		tcp flags ack,psh,syn,fin / ack,psh,syn,fin counter drop
		tcp flags ack,rst / ack,rst limit rate over 10/second counter drop
		tcp flags fin,psh / fin,psh limit rate over 10/second counter drop
		tcp flags ack,fin / ack,fin limit rate over 25/second counter drop
		tcp flags ack,rst,fin / ack,rst,fin counter drop
		tcp flags ack,rst,syn / ack,rst,syn counter drop
		tcp flags ack,rst,syn,fin / ack,rst,syn,fin counter drop
		tcp flags ack,syn,fin / ack,syn,fin counter drop
		tcp flags psh,rst,fin / psh,rst,fin counter drop
		tcp flags psh,rst,syn / psh,rst,syn counter drop
		tcp flags psh,rst,syn,fin / psh,rst,syn,fin counter drop
		tcp flags psh,syn,fin / psh,syn,fin counter drop
		tcp flags rst,fin / rst,fin counter drop
		tcp flags rst,syn,fin / rst,syn,fin counter drop
		tcp flags syn,fin / syn,fin counter drop
		tcp flags urg,ack / urg,ack counter drop
		tcp flags urg,ack,fin / urg,ack,fin counter drop
		tcp flags urg,ack,psh / urg,ack,psh counter drop
		tcp flags urg,ack,psh,fin / urg,ack,psh,fin counter drop
		tcp flags urg,ack,psh,rst / urg,ack,psh,rst counter drop
		tcp flags urg,ack,psh,rst,fin / urg,ack,psh,rst,fin counter drop
		tcp flags urg,ack,psh,syn / urg,ack,psh,syn counter drop
		tcp flags urg,ack,psh,syn,fin / urg,ack,psh,syn,fin counter drop
		tcp flags urg,ack,rst / urg,ack,rst counter drop
		tcp flags urg,ack,rst,fin / urg,ack,rst,fin counter drop
		tcp flags urg,ack,rst,syn / urg,ack,rst,syn counter drop
		tcp flags urg,ack,rst,syn,fin / urg,ack,rst,syn,fin counter drop
		tcp flags urg,ack,syn / urg,ack,syn counter drop
		tcp flags urg,ack,syn,fin / urg,ack,syn,fin counter drop
		tcp flags urg,fin / urg,fin counter drop
		tcp flags urg,psh / urg,psh counter drop
		tcp flags urg,psh,fin / urg,psh,fin counter drop
		tcp flags urg,psh,rst / urg,psh,rst counter drop
		tcp flags urg,psh,rst,fin / urg,psh,rst,fin counter drop
		tcp flags urg,psh,rst,syn / urg,psh,rst,syn counter drop
		tcp flags urg,psh,rst,syn,fin / urg,psh,rst,syn,fin counter drop
		tcp flags urg,psh,syn / urg,psh,syn counter drop
		tcp flags urg,psh,syn,fin / urg,psh,syn,fin counter drop
		tcp flags urg,rst / urg,rst counter drop
		tcp flags urg,rst,fin / urg,rst,fin counter drop
		tcp flags urg,rst,syn / urg,rst,syn counter drop
		tcp flags urg,rst,syn,fin / urg,rst,syn,fin counter drop
		tcp flags urg,syn / urg,syn counter drop
		tcp flags urg,syn,fin / urg,syn,fin counter drop

		# IP FRAGMENTS
		ip frag-off & 0x1fff != 0 counter drop

		# TCP NULL
		tcp flags & (fin|syn|rst|psh|ack|urg) == 0x0 counter drop

		# TCP MSS
		tcp flags syn \
			tcp option maxseg size 1-535 \
			counter drop

		}

}

     table inet DDOS {
	chain drop_ddos {
		type filter hook prerouting priority -155;

		# CT INVALID
		ct state invalid counter drop

		udp sport 1-65535 ct state new jump udp_flood

		# TCP SYN (CT NEW)
		tcp flags & (fin|syn|rst|ack) != syn \
			ct state new \
			counter drop

		ct state established, related counter accept

        }
    }
TABLE

if [ $wan_input_drop_enable -ge 1 ]; then

nft -f - <<TABLE

table inet DDOS {
	chain drop_ddos {
		type filter hook prerouting priority -155;

		iifname { $wan_device } goto reject_drop

        }
    }
TABLE

fi

if [ $wireguard_input_drop_enable -ge 1 ]; then

nft -f - <<TABLE

table inet DDOS {
	chain drop_ddos {
		type filter hook prerouting priority -155;

		iifname "Wg0" goto reject_drop
      }
   }
TABLE

fi

if [ $bogon -ge 1 ]; then

nft -f - <<TABLE

		table netdev NETDEV {

  	  chain ingress {
        	type filter hook ingress devices = { $wan_device } priority -495;

		ip saddr { $forward_router } counter accept

		ip saddr { $bogon_adresses } counter drop

		ip6 saddr { $bogon_ipv6_adresses } counter drop

}

      }

		table inet DDOS {

  	  chain drop_forward {
        	type filter hook forward priority filter -5;

        ip daddr { $forward_router } counter accept

	oifname { $wan_device } ip daddr { $bogon_adresses } counter reject with icmp type host-unreachable

	oifname { $wan_device } ip6 daddr { $bogon_ipv6_adresses } counter reject with icmpv6 type no-route

           }

	chain drop_postrouting {
                 type filter hook postrouting priority filter +5;

         ip daddr { $forward_router } counter accept


         oifname { $wan_device } ip daddr { $bogon_adresses } counter drop

	oifname { $wan_device } ip6 daddr { $bogon_ipv6_adresses } counter drop

         }

}

TABLE

fi

$verbose

exit 0
  • added arp request limiter

Most of tcp flags stuff is covered by tcp_loose sysctl,
More like if there is any improvement achievable to current default rate limiter.

Look at the code properly the syn limiter is placed in the input hook which is incomplete because everything that goes into the internal network can still be flooded via the forward hook, and in my script the filter is in the ingress hook which makes processing much faster because the packets are picked up directly at the network card and do not have to pass through so many stations, which reduces the computing effort. The disadvantage is that the script does not run on virtual machines because they do not have an ingress hook.

But then how do you browse the web (iifname is slow vs iif)

Of course, the limiter is only connected to the input of the Wan port, but everything outgoing is not processed by it at all, and when you surf the Internet normally, you send the syn packet and not the server. I use iifname because of Wireguard because it may or may not be there and iif cannot process a missing interface.

1 Like

I was thinking more along lines of mangle-prerouting ct-state new socket etc.

I also use this for port scan detection or Udp flood, but with the Syn flood it makes no sense because discarding in the Ingress hook is simply faster (I read two to three times as fast), which is why there are various invalid flag combinations in there.

I looked at it, you could optimize bitops with nft -c -o -f your.nft

Thanks but there doesn't seem to be anything to optimize the only thing that's messed up are the enable disable options and that's the shell part that has nothing to do with nftables.

Sneek-peek

Merging:
q.nft:4:3-42: 		tcp flags syn,psh / syn,psh counter drop
q.nft:5:3-50: 		tcp flags ack,psh,rst / ack,psh,rst counter drop
q.nft:6:3-58: 		tcp flags ack,psh,rst,fin / ack,psh,rst,fin counter drop
q.nft:7:3-58: 		tcp flags ack,psh,rst,syn / ack,psh,rst,syn counter drop
q.nft:8:3-66: 		tcp flags ack,psh,rst,syn,fin / ack,psh,rst,syn,fin counter drop
q.nft:9:3-50: 		tcp flags ack,psh,syn / ack,psh,syn counter drop
q.nft:10:3-58: 		tcp flags ack,psh,syn,fin / ack,psh,syn,fin counter drop
into:
	tcp flags syn,psh / { syn,psh, ack,psh,rst, ack,psh,rst,fin, ack,psh,rst,syn, ack,psh,rst,syn,fin, ack,psh,syn, ack,psh,syn,fin } counter drop

Are you sure this is how it works? Especially in the area of flags the nftables documentation is quite incomplete and partly incorrect. If you want you can change the script yourself and post it, I have absolutely nothing against it, but write what has been changed.

When it does not crash...

I don't know, you have to test it, I know that the version I posted works on the stable and snapshot version. At the end of the day, the question is whether it does any good at all apart from less code.

Well - it extracts packet data once then does multiple matches on it.

As far as I know, the flags are set in the header and do not have to be extracted, but it may well be that it is faster this way. My problem is whether this achieves exactly the same effect because the nftables flag documentation is very opaque, for example I found different flag spellings for the same result which were all accepted by nftables.

Header is copied from skb on each rule, then matched with bitops.
it is not meta ct meta socket meta that is reusable for free.

Well, maybe I haven't dealt with the functionality of nftables but it doesn't matter anyway because it doesn't work that way anyway

/dev/stdin:13:23-23: Error: syntax error, unexpected '{'
		tcp flags syn,psh / { syn,psh, ack,psh,rst, ack,psh,rst,fin, ack,psh,rst,syn, ack,psh,rst,syn,fin, ack,psh,syn, ack,psh,syn,fin } counter drop

Most of those are marked invalid by conntrack.

https://wiki.nftables.org/wiki-nftables/index.php/Netfilter_hooks Look where my filter starts (ingress hook) and in which area Conntrack works my filter discards these packets much earlier, which saves computing power because the subsequent stations do not have to run through until conntrack discards them.

1 Like

Bytecode probably costs more.