iptables-legacy + iptables-nft = iptables-broken ?

Rafael David Tinoco rafaeldtinoco at ubuntu.com
Wed Jan 27 19:44:59 UTC 2021


Hello devs...

Some of you know, I have been developing a tool called "conntracker" at https://github.com/rafaeldtinoco/conntracker. While developing it, I have faced many "issues" regarding iptables differences among Bionic and Focal/Groovy  and I would like to confirm one behavior I observed today while testing the tool.

The conntracker tool is very simple: it uses conntrack kernel module to identify all flows and keep those in memory. It also traces (-j TRACE) all observed flows and populate observed flows for the places the packets have been. I also have an option to trace *everything* (and not only observed, by conntrack, flows). 

Quick example:

 TCPv4 [           1] src = 127.0.0.1 (port=1024) to dst = 127.0.0.1 (port=50390) (confirmed)
                                table: mangle, chain: INPUT, type: rule, position: 4
                                table: mangle, chain: OUTPUT, type: rule, position: 4
                                table: mangle, chain: POSTROUTING, type: policy
                                table: mangle, chain: PREROUTING, type: policy
                                table: filter, chain: INPUT, type: rule, position: 4
                                table: filter, chain: OUTPUT, type: rule, position: 4
 UDPv4 [           1] src = 192.168.100.13 (port=1024) to dst = 224.0.0.251 (port=5353)
                                table: mangle, chain: INPUT, type: rule, position: 1
                                table: mangle, chain: PREROUTING, type: policy
                                table: filter, chain: INPUT, type: rule, position: 1
 TCPv6 [           0] src = 2606:2800:258:80d:3b4:1d2d:1c2:26bb (port=443) to dst = 2001:1284:f013:8c06:921b:eff:fe0c:59f1 (port=43768) (confirmed)
                                table: mangle, chain: INPUT, type: rule, position: 4
                                table: mangle, chain: PREROUTING, type: policy
                                table: filter, chain: INPUT, type: rule, position: 4

As you can notice, policy indicates the flow has been processed by the chain policy and not by a iptables rule. If a rule processed the flow, in that table/chain combination, you get a position (the rule "to blame" for the action on that flow -> ACCEPT/REJECT/DROP).

With this introduction...

Thing is we currently have "2 firewalls" available and enabled for Focal/Groovy/Hirsute, right ? The old "xtables" one and the "nft" new one. The nf-tables firewall tries to "mimic" what we have with the xtables/netfilter/iptables combination by creating tables with similar chains:

$ sudo nft list tables
table ip filter
table ip6 filter
table bridge filter
table ip nat
table ip raw
table ip6 raw
table ip mangle
table ip6 mangle
table ip6 nat

$ sudo nft list table nat
table ip nat {
        chain PREROUTING {
                type nat hook prerouting priority dstnat; policy accept;
        }

        chain INPUT {
                type nat hook input priority 100; policy accept;
        }

        chain POSTROUTING {
                type nat hook postrouting priority srcnat; policy accept;
        }

        chain OUTPUT {
                type nat hook output priority -100; policy accept;
        }
}

AND, at the same time, we still have the legacy iptables working:

$ sudo iptables-legacy -t nat -L
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination         

Chain INPUT (policy ACCEPT)
target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination         

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination      


I know I could use only legacy xtables/netfilter iptables, or only the nft one... but the default is to have both enabled, right ?  I have observed some weird behaviors when using both and I wanted to clarify if this is really what we intended when we enabled both firewall codes simultaneously....

If I change the default policy for a table/chain in iptables-legacy, it won't work unless I do the same thing in iptables-nft and vice versa. Example:

$ sudo iptables-nft -t filter -L -n --line-numbers 
# Warning: iptables-legacy tables present, use iptables-legacy to see them
Chain INPUT (policy DROP)
num  target     prot opt source               destination         
1    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           
2    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
3    ACCEPT     icmpv6--  0.0.0.0/0            0.0.0.0/0           
4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           

Chain FORWARD (policy DROP)
num  target     prot opt source               destination         
1    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           
2    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
3    ACCEPT     icmpv6--  0.0.0.0/0            0.0.0.0/0           
4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0           

Chain OUTPUT (policy DROP)
num  target     prot opt source               destination         
1    ACCEPT     udp  --  0.0.0.0/0            0.0.0.0/0           
2    ACCEPT     icmp --  0.0.0.0/0            0.0.0.0/0           
3    ACCEPT     icmpv6--  0.0.0.0/0            0.0.0.0/0           
4    ACCEPT     tcp  --  0.0.0.0/0            0.0.0.0/0      

$ sudo iptables-legacy -t filter -L -n --line-numbers 
Chain INPUT (policy ACCEPT)
num  target     prot opt source               destination         

Chain FORWARD (policy ACCEPT)
num  target     prot opt source               destination         

Chain OUTPUT (policy ACCEPT)
num  target     prot opt source               destination   

Like here:

 TCPv4 [           6] src = 192.168.100.203 (port=1024) to dst = 104.244.42.2 (port=443) (confirmed)
                                table: mangle, chain: OUTPUT, type: policy
                                table: mangle, chain: POSTROUTING, type: policy
                                table: nat, chain: OUTPUT, type: policy
                                table: nat, chain: POSTROUTING, type: policy
                                table: filter, chain: OUTPUT, type: policy

This means that the POLICY has acted in all those table/chains combination, NOT the rules I have in place. So, if I change the default policy using iptables-legacy to -j DROP... then my iptables-nft rules don't work at all. This means that one should really use iptables-legacy (and not nft) to setup default policies. Is that correct ? And.. if such, why to have nft enabled by default if it cannot work solo when xtables are enabled.

Concern:

One could have a default Ubuntu server installation, with a bunch of iptables (legacy) rules brought by Bionic, for example, and think is secure but.. "-j DROP" for the DEFAULT iptables-nft could be doing nothing regarding flow controlling.

Is that so ? Did I miss anything ? Should we do something if I did not ?

-rafaeldtinoco



More information about the ubuntu-devel mailing list