Hi, I've just confirmed that I can't make a rule that matches ct status != dnat. TL;DR I have to use ct status {expected,seen-reply,assured,confirmed,snat,dying} instead. This feels like a bug. This page suggests using != and also lists all the valid values (or at least, I hope that's all the valid values): https://wiki.nftables.org/wiki-nftables/index.php/Quick_reference-nftables_in_10_minutes#Ct I have a firewalld-created dnat rule. chain nat_PRE_wan_allow { tcp dport 5007 dnat to xx.xx.xx.xx:7 } There's a rule that sets a ct mark depending on the interface (so replies will correctly return, avoiding a problem with default routes). chain PerIfRoute_Prerouting { ct state established,related ct direction reply meta mark set ct mark iifname "wwan0" ct state new ct mark set 4 iifname "wwan0" ct state new ct mark 4 meta mark set 4 ct direction original meta mark set 0 } I have a forwarding rule that wants to match ct status != dnat. chain PerIfRoute_Forward { ct status != dnat ct mark != 0 ct mark set 0 } I turned on nftrace and see that this condition matches?! trace id 04c0aaee ip firewalld nat_PRE_wan_allow packet: iif "wwan0" ip saddr xx.xx.xx.xx ip daddr xx.xx.xx.xx ip dscp cs0 ip ecn not-ect ip ttl 50 ip id 2777 ip length 60 tcp sport 20692 tcp dport 5007 tcp flags == syn tcp window 64240 trace id 04c0aaee ip firewalld nat_PRE_wan_allow rule tcp dport 5007 dnat to xx.xx.xx.xx:7 (verdict accept) trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Prerouting packet: iif "wwan0" ip saddr xx.xx.xx.xx ip daddr xx.xx.xx.xx ip dscp cs0 ip ecn not-ect ip ttl 50 ip id 2777 ip protocol tcp ip length 60 tcp sport 20692 tcp dport 7 tcp flags == syn tcp window 64240 trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Markers rule iifname "wwan0" ct state new ct mark set 0x00000004 (verdict continue) trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Markers rule iifname "wwan0" ct state new ct mark 0x00000004 meta mark set 0x00000004 (verdict continue) trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Prerouting rule ct direction original meta mark set 0x00000000 (verdict continue) trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Forward packet: iif "wwan0" oif "net2" ip saddr xx.xx.xx.xx ip daddr xx.xx.xx.xx ip dscp cs0 ip ecn not-ect ip ttl 49 ip id 2777 ip protocol tcp ip length 60 tcp sport 20692 tcp dport 7 tcp flags == syn tcp window 64240 trace id 04c0aaee inet PerIfRouteTable PerIfRoute_Forward rule ct status != dnat ct mark != 0x00000000 ct mark set 0x00000000 (verdict continue) I did some tests... Positive matching works as expected nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne expected nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne seen-reply nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne assured nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne confirmed nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne snat nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne dnat nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status ne dying nftrace set 1 Gives trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status dnat meta nftrace set 1 (verdict continue) But the != matching always matches nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != expected nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != seen-reply nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != assured nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != confirmed nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != snat nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != dnat nftrace set 1 nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status != dying nftrace set 1 Gives trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != expected meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != seen-reply meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != assured meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != confirmed meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != snat meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != dnat meta nftrace set 1 (verdict continue) trace id 6bc7de6c inet PerIfRouteTable PerIfRoute_Forward rule ct status != dying meta nftrace set 1 (verdict continue) If I rewrite my match to catch all the valid values, it works. nft insert rule inet PerIfRouteTable PerIfRoute_Forward position 2 ct status '{expected,seen-reply,assured,confirmed,snat,dying}' nftrace set 1 This does not match when ct status == dnat. With all that debugging in, I saw a reply packet too. It ... complicates things? trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward packet: iif "net2" oif "wwan0" ether saddr xx:xx:xx:xx:xx:xx ether daddr xx:xx:xx:xx:xx:xx ip saddr xx.xx.xx.xx ip daddr xx.xx.xx.xx ip dscp cs0 ip ecn not-ect ip ttl 63 ip id 0 ip protocol tcp ip length 60 tcp sport 7 tcp dport 57444 tcp flags == 0x12 tcp window 28960 trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status seen-reply meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status confirmed meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status snat meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status dnat meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != expected meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != seen-reply meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != assured meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != confirmed meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != snat meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != dnat meta nftrace set 1 (verdict continue) trace id 2df38b4d inet PerIfRouteTable PerIfRoute_Forward rule ct status != dying meta nftrace set 1 (verdict continue) Matching multiple statuses?! I had a look and it seems ct status is a bitmask underneath, not a single value. I guess the = operator is doing "is this value in the bitmask" logic? I don't know what != is doing, but I'd expect the "opposite" (ie. "is this value not in the bitmask"). I'm using Yocto (Dunfell) which has given me these versions: - Linux 5.4 - nftables 0.9.3 - libnftnl 1.1.5 Lincoln