Skip to content
LinkState
Go back

The packet matched before it was reassembled

Introduction to Fragmented Traffic

Definition and Causes

IP fragmentation occurs when an IP packet exceeds the link MTU. The originating host (or a router doing PMTUD) splits the datagram into fragments, each carrying:

Common causes:

Impact on Network Security

Understanding where defragmentation occurs in the Linux packet path—and how tools like tcpdump and nftables observe packets—is key to writing correct rules for fragmented traffic.


TCPDump and Nftables Overview

TCPDump Capture Point

tcpdump uses libpcap and captures packets as they are delivered to a packet socket:

Socket typeWhat is seen
PF_PACKET, SOCK_RAW (raw)Bytes on the wire after the NIC driver but before any stack processing (netif_receive_skb).
SOCK_DGRAM (cooked)After IP processing but before delivery to higher layers (roughly post‑ip_rcv_finish).

Most invocations (tcpdump -i eth0) use a raw socket, so they show the exact wire image, including any IP fragments before kernel re‑assembly.

Nftables Hooks and Defragmentation

nftables hooks into Netfilter at defined points:

HookNetfilter constantPacket state
preroutingNF_INET_PRE_ROUTINGAfter NIC, before routing; IP header only (no transport header if fragmented).
inputNF_INET_LOCAL_INAfter routing decision, after IP defragmentation (if destined for a local socket).
forwardNF_INET_FORWARDAfter routing decision, after IP defragmentation (if to be forwarded).
outputNF_INET_LOCAL_OUTLocally generated, after transport header creation, before routing.
postroutingNF_INET_POST_ROUTINGJust before transmission, after routing and possible outgoing fragmentation.

IP defragmentation runs in ip_rcv_finish, which sits after NF_INET_PRE_ROUTING and before NF_INET_LOCAL_IN / NF_INET_FORWARD. Therefore:

The explicit defrag statement can force early re‑assembly at any hook, overriding the default timing.

Comparing TCPDump and Nftables

Because tcpdump captures at the wire (pre‑defrag) while nftables input/forward hooks see post‑defrag packets, comparing the two reveals where defragmentation occurred. Inserting a defrag statement before or after a rule lets you shift the evaluation point and observe the effect in both packet counters (nft list chain …) and the capture file.


Competing Explanations for Rules that Never Match

Hypothesis 1: Evaluation Before Re‑assembly

The rule is evaluated before the kernel has re‑assembled the fragments, so Layer‑4 fields are missing in the packets that reach the hook.

Hypothesis 2: Defragmentation Delays or Skips

The Linux IP re‑assembly algorithm may postpone or skip re‑assembly under certain conditions:

In these cases the input/forward hooks never see a complete packet, so the rule never matches.

Hypothesis 3: Rule Ordering / Short‑circuit

A rule that inspects Layer‑4 data is placed after a rule that accepts, returns, or mutates the packet (e.g., changes the destination).


Testing the Hypotheses

Experimental Setup

ComponentDetails
Sender (Host A)Ubuntu 22.04, Linux 6.5, scapy v2.5 to build fragmented UDP packets (total length 1800 B → guaranteed fragmentation on MTU 1500).
Receiver / Test Box (Host B)Same OS, two vEth pairs: eth0 (linked to Host A) and eth1 (loopback to a dummy sink).
nftablesTable ip filter with chains in prerouting, input, and forward hooks to place rules at different points.
Traffic capturetcpdump -i eth0 -w capture.pcap -s 0 on Host B (raw socket).
Verificationnft list chain ip filter <chain> to read packet/byte counters; tcpdump -nn -r capture.pcap to inspect what was seen on the wire.
Fragmentation controlNo fragmentation: ping -M do -s 1472 <dest>
Force fragmentation: ping -s 1500 <dest> (ICMP payload > MTU‑header) or the scapy UDP flow described above.

Methodology

  1. Baseline – Send fragmented UDP traffic with a payload containing a distinctive byte pattern (e.g., 0xdeadbeef).
  2. Capture – Run tcpdump on Host B to record the wire image.
  3. nftables rules – Create three identical rules that match the UDP payload (payload 0xdeadbeef) and increment a counter:
    • One in prerouting
    • One in input
    • One in forward
      (Add a defrag statement before the rule in a separate test to force early re‑assembly.)
  4. Collect counters – After a fixed interval, run nft list chain ip filter <chain> to see how many packets each rule counted.
  5. Analyze pcap – Use tcpdump -nn -r capture.pcap to verify whether the fragmented packets arrived as expected and whether any were dropped.
  6. Vary conditions – Repeat the test while:
    • Setting /proc/sys/net/ipv4/ip_no_frag_forward=1 (if available) to test Hypothesis 2.
    • Lowering net.ipv4.ipfrag_high_thresh to provoke queue drops.
    • Re‑ordering rules so a generic accept precedes the payload‑match rule (testing Hypothesis 3).

Expected Observations

HypothesisWhat you should see
1 – Early evaluationprerouting counter increments only on the first fragment; input/forward counters stay at zero (unless defrag is added, which then raises their counts).
2 – Defragmentation skipped/delayedEven with defrag or input/forward hooks, counters remain low; tcpdump shows fragments arriving but the kernel logs indicate they were dropped or forwarded whole (check ip_forward stats).
3 – Rule orderingAll hooks see the re‑assembled packet (counters rise) unless an earlier accept/return rule is present, in which case the payload‑match counter stays at zero despite visible fragments in the pcap.

Interpreting Results

By systematically varying hook placement, using defrag, and manipulating kernel re‑assembly parameters, you can pinpoint which explanation (or combination thereof) accounts for why a Layer‑4 nftables rule fails to match fragmented traffic.


Share this post on:

Previous Post
Why GRO and GSO make overlay captures lie
Next Post
XDP is not automatically the cheapest fast path