SYN Cookies are a clever, albeit somewhat obscure, way to defend against SYN flood attacks at the TCP layer, implemented within nftables.

Here’s nftables in action, protecting against a SYN flood. Imagine a server under attack:

# Simulate incoming SYN packets (this would be the attack)
# In a real scenario, this traffic would come from malicious IPs.
# For demonstration, we'll just generate a lot of SYN packets to ourselves.
for i in {1..100000}; do
    echo -en "\x53\x59\x4e" | nc -u 127.0.0.1 8080
done

Now, let’s set up nftables to handle this. We’ll define a basic table and chain, and then add our SYN cookie rule.

First, create a table and a chain for incoming packets:

sudo nft add table ip filter
sudo nft add chain ip filter input { type filter hook input priority 0 \; }

Next, we add a rule to enable SYN cookies for incoming TCP connections on port 8080:

sudo nft add rule ip filter input tcp dport 8080 ct state new add synproxy

Let’s break down what’s happening here:

  • sudo nft add rule ip filter input: We’re adding a rule to the input chain of the filter table for IPv4.
  • tcp dport 8080: This rule specifically targets incoming TCP packets destined for port 8080.
  • ct state new: This matches packets that are initiating a new connection (the SYN packet). The ct (connection tracking) module is crucial here.
  • add synproxy: This is the magic. When a new incoming TCP connection is detected, nftables (leveraging the kernel’s SYNProxy functionality) will intercept the SYN packet. Instead of immediately allocating resources for a connection, it will send back a specially crafted SYN-ACK packet. This SYN-ACK contains a "cookie" derived from the original SYN packet’s details (source IP, source port, destination IP, destination port, and a secret kernel key). The server doesn’t actually create a full connection entry in its state table at this point. It only remembers that it sent a SYN-ACK with a specific cookie.

If the client is legitimate, it will respond with an ACK packet. The kernel then checks if this ACK packet contains the correct cookie. If it does, the kernel reconstructs the original SYN packet’s information from the cookie and then creates a full connection tracking entry, allowing the connection to proceed normally. If the ACK is missing or invalid, the packet is dropped, and no server resources were wasted.

This prevents an attacker from overwhelming the server with SYN packets because the server only expends minimal resources (sending a SYN-ACK and performing a quick cookie check) until a valid ACK is received. A legitimate user’s SYN-ACK will always be valid, while a spoofed SYN from an attacker will never receive a valid ACK back.

The problem this solves is the "half-open connection" exhaustion. In a traditional TCP handshake, the server allocates resources for a connection upon receiving the initial SYN. An attacker can send thousands or millions of SYN packets from spoofed IP addresses. The server would dutifully send SYN-ACKs, but since the source IPs are spoofed, no ACKs would ever arrive. The server’s connection table would fill up, preventing legitimate users from establishing connections. SYN Cookies bypass this by delaying resource allocation until the client proves it’s a real endpoint.

The core idea is that the information needed to reconstruct the connection state (source IP, port, destination IP, port) can be encoded into the TCP sequence number of the SYN-ACK packet sent back by the server. The server’s SYN-ACK sequence number is typically 1. With SYN Cookies, the server sends a SYN-ACK with a sequence number that is a cryptographic hash of the original connection details and a secret server key. The client, upon receiving this, would typically send an ACK with a sequence number of (initial_seq_from_server) + 1. The server can then verify this ACK by recalculating the hash. If the hash matches, it knows the client is legitimate and can proceed to establish the connection.

The add synproxy target in nftables is essentially a user-space interface to the kernel’s SYNProxy module, which implements this logic. It’s a highly efficient way to offload this protection directly into the network packet filtering framework.

The most surprising thing about SYN Cookies is that they work by essentially lying to the client initially. The server pretends to accept the connection and sends a SYN-ACK, but it doesn’t actually commit to the connection until the client proves itself. This clever deception is what allows it to filter out malicious traffic without consuming significant resources.

When you enable synproxy, the kernel does a few things:

  1. It intercepts the SYN packet.
  2. It generates a cookie based on connection details and a secret key.
  3. It sends a SYN-ACK with a sequence number that includes this cookie. The actual sequence number is cookie + 1.
  4. It does not create a connection tracking entry (conntrack entry) for this connection yet.
  5. When an ACK packet arrives, the kernel checks if the sequence number in the ACK is (cookie + 1) + 1. If it is, it means the client received the SYN-ACK and is responding correctly.
  6. Only then does the kernel create the conntrack entry and allow the packet to pass through to the application.

This means that for every SYN packet during an attack, the server only sends a single SYN-ACK and performs a quick hash calculation, rather than allocating memory for a full connection state.

If you were to see an error after implementing SYN cookies, it would likely be related to the connection tracking module itself, or perhaps an application that doesn’t correctly handle the delayed establishment of TCP connections. The next immediate problem you might encounter is an application not receiving connections if it doesn’t properly handle the TCP handshake’s initial SYN-ACK phase, though this is rare with standard TCP stacks.

Want structured learning?

Take the full Nftables course →