5

I want to handle ip fragments in user-space, and I am using the iptables NF_QUEUE to direct packets to user-space.

The problem is that IPv4 packets are always re-assembled and delivered as one packet rather than individual fragments. For IPv6, fragments are delivered as they should.

I thought that the conntracker might be causing it and disabled it in the raw iptables table, but it turns out that the packet is already re-assembled when it reaches the raw table:

# iptables -t raw -nvL
Chain PREROUTING (policy ACCEPT 58 packets, 62981 bytes)
 pkts bytes target     prot opt in     out     source               destination         
    1 30028 CT         all  --  *      *       0.0.0.0/0            10.0.0.0/24          NOTRACK

This is when sending a 30000 byte UDP packet over IPv4. The corresponding for IPv6:

# ip6tables -t raw -nvL
Chain PREROUTING (policy ACCEPT 46 packets, 62304 bytes)
 pkts bytes target     prot opt in     out     source               destination         
   21 31016 CT         all      *      *       ::/0                 1000::               NOTRACK

This is in a virtual environment kvm/qemu with virtio network devices, mtu=1500. Some HW offload does not seem to cause this, since I can see all IPv4 fragments with tcpdump -ni eth2 host 10.0.0.0.

So my question is what in the Linux kernel can force IPv4 packets to be re-assembled before the raw/PREROUTING netfilter chain?

I suspect "ingress/qdisc" as it is in between AF_PACKET (tcpdump) and the raw/PREROUTING chain, but I can't find the problem.

Packet flow: https://upload.wikimedia.org/wikipedia/commons/3/37/Netfilter-packet-flow.svg

1
  • ipV6 doesn't supports fragmentation while on the wire ie by routers. The fragmentation you see is done by the sender. Commented Jun 20, 2021 at 14:54

2 Answers 2

5

Whenever conntrack is in use, mainly for:

  • stateful firewalling (-m conntrack ...)
  • NAT (-t nat ...)

An additional hidden facility gets loaded, provided by the kernel modules nf_defrag_ipv4 and nf_defrag_ipv6. This facility hooks into network prerouting at priority -400: that's before iptables' raw table which hooks at priority -300. After a packet(s) traverses nf_defrag_ipv[46] no fragment ever exists: packets were reassembled early. The goal is that the various protocol inspectors in Netfilter and iptables can get all of the packet contents, including for example UDP destination port: this information will be present only in the first fragment.

So to avoid this, the -j NOTRACK (obsoleted by -j CT --notrack) in the raw table is not enough.

One can:

  • never use conntrack directly (stateful rules) or indirectly (NAT),

  • or create a new network namespace

    to handle directly traffic (most likely using a stolen physical interface, or a macvlan interface, or being bridged but not routed by the host) and making sure that no stateful rule happens in this namespace. the defragmentation facility doesn't hook in the network namespace as long as nothing forces it to do so (and probably also with a recent enough kernel)

  • or have a chain that hooks before priority -400. This is actually possible in recent enough kernels:

    • for iptables-legacy since kernel (probably) >= 4.16

      # modinfo -p iptable_raw
      raw_before_defrag:Enable raw table before defrag (bool)
      

      flush raw table, unload module and reload it with (and adjust /etc/modprobe.d/):

      modprobe iptable_raw raw_before_defrag=1
      
    • for nftables

      Just create chains with priority lower than -400, eg:

      nft add table ip handlefrag
      nft add chain ip handlefrag predefrag '{ type filter hook prerouting priority -450; policy accept; }'
      nft add rule ip handlefrag predefrag ip 'frag-off & 0x3fff != 0' notrack 
      

      (to deal only with following fragments, not the first, replace 0x3fff with 0x1fff)

      For IPv6 the method is different, because the fragment header might not be the next header. But nft provides an easy expression in its man: exthdr frag exists to detect packets that are fragments.

    • nothing exists for the iptables-nft API (which is default on many distributions like Debian): it doesn't use the module iptable_raw and doesn't have an option to create the actual nftables chain at priority -450.

      So if your command's output looks like this:

      # iptables -V
      iptables v1.8.7 (nf_tables)
      

      you can't use this alone along conntrack. You must revert to iptables-legacy or switch to nft, or...

    • what can yet still be done, is to mix nftables (rules above) to tag the packets as notrack before they are defragmented, and continue with iptables to handle the remaining part. There's no issue in using nftables and iptables at the same time, as long one understands the order of operations as seen in the schematic of Netfilter linked by OP.

9
  • Perfect answer, thanks! I had a MASQUERADE rule active, but even when that was removed and the xt_MASQUERADE module was also removed, packets were re-assembled. I gues once the "additional hidden facility gets loaded" (good wording) the namespace is "contaminated" with defragmentation. Starting with an empty netns works beautifully.
    – lgekman
    Commented May 22, 2021 at 8:08
  • The module that has to be removed for init (and all other) network namespaces is nf_defrag_ipv4 (or nf_defrag_ipv6). Then in addition, even when this module is loaded, the other (non-init) network namespace is only affected if it itself used one way or an other something triggering early defragmentation. Example to get a clean namespace: ip netns add mycleannamespace. Note also that any test must be made from an other "clean" network stack. If tests are then made from the host, chances are the host will defragment that's why I wrote the host might bridge but not route such namespace.
    – A.B
    Commented May 22, 2021 at 10:17
  • I got the "unwanted defrag" in a clean netns on a system. After quite some time of troubleshooting I got it working by uninstalling openvswitch. It uses the nf_defrag modules and seem to trig defragmentation. I just discovered this and have not investigated it further. But it may save for other to know this.
    – lgekman
    Commented Jun 21, 2021 at 16:36
  • Just to load the "openvswitch" module is not enough to trig the unwanted defragmentation. There must be something else done also on Ubuntu 20.04 when the openvswitch service is enabled.
    – lgekman
    Commented Jun 22, 2021 at 8:23
  • 1
    The behaivor has changed in linux-5.13. The re-assembly function is actually disabled on a "iptables -t nat -F" even if there had been masquerade rules there before.
    – lgekman
    Commented Oct 13, 2021 at 15:38
0

iptables has (for IPv4) the -f flag. Adding it should work. Check also with the INPUT chain.

[!] -f, --fragment

This means that the rule only refers to second and further IPv4 fragments of fragmented packets. Since there is no way to tell the source or destination ports of such a packet (or ICMP type), such a packet will not match any rules which specify them. When the "!" argument precedes the "-f" flag, the rule will only match head fragments, or unfragmented packets. This option is IPv4 specific, it is not available in ip6tables.

1
  • I added iptables -t mangle -I PREROUTING 1 -f -j ACCEPT just to get a counter if hit. But the counter shows no hits, no fragments. I makes sense since the packet is reassembled. But it is a packet > MTU so it was fragmented.
    – lgekman
    Commented May 21, 2021 at 16:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .