I have been working on implementing synproxy in nftables. In this post I am going to explain its usage basics to mitigate TCP SYN-flood attacks!

What a TCP SYN-flood attack is?

When a connection is created in Linux the kernel allocates system’s resources to handle the connection. This is translated into a lock for each socket in the “listen” state. This does not happen only with SYN packets, also with all the three-way handshake states.
Therefore, an attacker can mass send fake packets to the victim to exhaust its resources.

How does synproxy work?

The idea of synproxy is to prevent false connection starts from consuming victim resources. To do this, it acts as a transparent proxy that responds to the attacker without tracking the connection until the 3-way handshake is complete.
synproxy-diagram

Statement syntax

nft synproxy mss 14600 wscale 7 timestamp sack-perm

mss: This option allow us to indicate the mss value announced to the client. It should match the backend mss.
wscale: This option allow us to indicate the wscale value announced to the client. Again, it should match the backend wscale.
timestamp: This flag pass to the client the timestamp option (this is needed for selective acknowledge and window scaling).
sack-perm: This flag pass to the client the selective acknowledge option.\

Configuring synproxy

First, we need to determine the tcp options used by the backend from an external system.

$ tcpdump -pni eth0 -c 1 'tcp[tcpflags] == (tcp-syn|tcp-ack)' port 80 & telnet 192.0.2.42 80


Then we need to switch tcp_loose mode off, so conntrack will mark out-of-flow packets as state INVALID.

$ /sbin/sysctl -w net/netfilter/nf_conntrack_tcp_loose=0


Make SYN packets untracked.

table ip x {
	chain y {
        	type filter hook prerouting priority raw; policy accept;
                tcp dport 80 tcp flags syn notrack
        }
}


And finally catch the UNTRACKED SYN packets and INVALID states 3-way-handshake packets and send them to synproxy. Also, we need to drop invalid packets that were not matched by synproxy.

table ip x {
	chain z {
        	type filter hook input priority filter; policy accept;
                tcp dport 80 ct state { invalid, untracked } synproxy mss 1460 wscale 9 timestamp sack-perm
                ct state invalid drop
        }
}


The outcome ruleset should be something similar to this one below.

table ip x {
	chain y {
        	type filter hook prerouting priority raw; policy accept;
                tcp dport 80 tcp flags syn notrack
        }

        chain z {
        	type filter hook input priority filter; policy accept;
                tcp dport 80 ct state { invalid, untracked } synproxy mss 1460 wscale 9 timestamp sack-perm
                ct state invalid drop
        }
}


I hope this post has been useful to you! If you have any question don’t hesitate to contact me.