Wednesday 27 January 2021

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

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

--
ubuntu-devel mailing list
ubuntu-devel@lists.ubuntu.com
Modify settings or unsubscribe at: https://lists.ubuntu.com/mailman/listinfo/ubuntu-devel