Why did this packet get logged?

I have a very simple nftables setup.

My (virtual) machine is on 3 networks; ens2==LAN (10.0.0.5), ens8==Guest (10.100.100.5), ens9==IoT (10.100.200.5).

The rules allow all traffic on LAN, and restricted incoming traffic on guestnet/IoT (basically DNS, DHCP, NTP).

But I saw this packet logged, which appears to be an outgoing packet originating from my machine… but logged and blocked as a bad incoming packet?

[2009871.077457] IPTables-Dropped: IN=ens8 OUT= MAC=52:54:00:0c:d8:05:38:f9:d3:5c:84:b7:08:00 SRC=10.100.100.125 DST=10.100.100.5 LEN=56 TOS=0x00 PREC=0x00 TTL=64 ID=60390 PROTO=ICMP TYPE=3 CODE=3 [SRC=10.100.100.5 DST=10.100.100.125 LEN=144 TOS=0x00 PREC=0x00 TTL=64 ID=38608 PROTO=UDP SPT=53 DPT=19355 LEN=124 ] 

The MAC address 52:54:00:0c:d8:05 is the address of my machine. 10.100.100.5 is the address of my machine. 38:f9:d3:5c:84:b7 and 10.100.100.125 is a machine on my guest network. So this looks like a DNS reply from my machine to my client… but it was blocked!

So why did this hit the outgoing filter?

For completeness

# Clear out all the rules
flush ruleset

# Create empty structures
add table ip filter

# Define the 3 standard filters:
#     INPUT: Traffic targeting this machine
#    OUTPUT: Traffic originating from this machine
#   FORWARD: Traffic goin through this machine

add chain ip filter INPUT { type filter hook input priority 0; policy accept; }
add chain ip filter FORWARD { type filter hook forward priority 0; policy accept; }
add chain ip filter OUTPUT { type filter hook output priority 0; policy accept; }

# Log rules for input and forwarding
add chain ip filter LOGGER

# Logging rule for INPUT drops
add rule ip filter LOGGER limit rate 2/minute burst 5 packets counter log prefix "IPTables-Dropped: "
add rule ip filter LOGGER counter drop

# LAN can see everything
add rule ip filter INPUT iifname "ens2" counter accept

# Guestnet can DNS, DNS/TLS, DHCP, NTP
add rule ip filter INPUT iifname "ens8" tcp dport 53 counter accept
add rule ip filter INPUT iifname "ens8" udp dport 53 counter accept
add rule ip filter INPUT iifname "ens8" tcp dport 853 counter accept
add rule ip filter INPUT iifname "ens8" udp dport 853 counter accept
add rule ip filter INPUT iifname "ens8" udp dport 67 counter accept
add rule ip filter INPUT iifname "ens8" udp dport 123 counter accept

# IoT can DNS, DNS/TLS, DHCP, NTP
add rule ip filter INPUT iifname "ens9" tcp dport 53 counter accept
add rule ip filter INPUT iifname "ens9" udp dport 53 counter accept
add rule ip filter INPUT iifname "ens9" tcp dport 853 counter accept
add rule ip filter INPUT iifname "ens9" udp dport 853 counter accept
add rule ip filter INPUT iifname "ens9" udp dport 67 counter accept
add rule ip filter INPUT iifname "ens9" udp dport 123 counter accept

# Block all other incoming traffic and log it
add rule ip filter INPUT counter jump LOGGER

The really confusing bit is that the SRC/DST conflict in the same log line

SRC=10.100.100.125 DST=10.100.100.5 ... [SRC=10.100.100.5 DST=10.100.100.125

This looks more like some race condition in logging (multiple packets intermixed in one logging entry). Indicators:

  • The mac address is more than 6 bytes (which is not possible with ethernet)
  • There are two PROTO fields with values ICMP and UDP

So: This log entry should be ignored.

Do look at the actual active ruleset (with nft -a list ruleset).
That does show the counter stats too.

Your script shows use of LOGGER chain only on input hook.
Hence the thing that was logged was input, not output?

I have never used the ‘log’, so don’t know what a message contains.
Upstream says that it has ‘flags’. Perhaps they affect the details?

Yeah, originally I just had a “drop” rather than a log entry and it was the non-zero count that made me wonder what machine was doing “bad stuff” so I added the logging rule to see what was going on.

And this just made me more confused because, as you say, it’s only on the input chain.

Looking at the counts now, I can see the numbers match; there are now 11 packets that hit the “jump to LOGGER” rule at the end of the input chain, and 11 packets passing through the log line, and 11 packets hitting the drop (all as expected, so nothing else is calling LOGGER). And this matches 11 entries in dmesg, all referring to the same machine.

The MAC address field in log output always has two address in it; e.g on my router/firewall I log incoming attempts to access my SSH server and the MAC field has the address of my machine and my ISPs gateway; e.g.

IN=br-wan OUT= MAC=00:0d:b9:43:9c:cd:42:a6:77:4d:c0:f2:08:00 SRC=162.216.149.114 DST=my.ip LEN=44 TOS=0x00 PREC=0x00 TTL=245 ID=24981 PROTO=TCP SPT=54692 DPT=22 WINDOW=1024 RES=0x00 SYN URGP=0

My MAC is 00:0d:b9:43:9c:cd; the ISP gateway is 42:a6:77:4d:c0:f2. I’m not sure what the final 08:00 is

II this is a race condition then it’s repeatable 'cos I see the same thing 11 times in the past 12 hours, all referring to the same machine! The race would not be in the LOGGING; previously I had a simple “drop” rule and that was showing packets hitting it, and that’s when I added the log rule to see what was going on.

OK, it makes sense now!

The incoming packet being dropped was an “ICMP Port unreachable” (type 3, code 3) message and that packet was sent in response to a DNS answer my machine sent out; the original outgoing packet is inside the square brackets.

So my guess is the client machine may make DNS requests to multiple sources at the same time and when it gets a response from one of them it stops listening for any other responses, and so rejects any later “slower” packets.

And here we go; on this machine I can see this DNS query:

07-Feb-2025 04:51:04.260 client @0x7f768c7b08c8 10.100.100.125#22948 (app.slack.com): query: app.slack.com IN HTTPS + (10.100.100.5)

And on my router (where I run the other DNS server) I can also see

07-Feb-2025 04:51:04.244 client @0x7fd910161ae8 10.100.100.125#46718 (app.slack.com): query: app.slack.com IN HTTPS + (10.100.100.1)

So, yeah, the client made the same request of two DNS servers, got a response from one and rejected the response from the other. And it was that rejection that got logged.

1 Like

The default input chain created by FirewallD on el9 looks like:

	chain filter_INPUT {
		type filter hook input priority filter + 10; policy accept;
		ct state { established, related } accept
		ct status dnat accept
		iifname "lo" accept
		ct state invalid drop
		jump filter_INPUT_ZONES
		reject with icmpx admin-prohibited
	}

and the default “public” zone (under/within filter_INPUT_ZONES) is essentially:

		tcp dport 22 accept
		ip6 daddr fe80::/64 udp dport 546 accept
		tcp dport 9090 accept
		meta l4proto { icmp, ipv6-icmp } accept
		reject with icmpx admin-prohibited

Of these

ct state { established, related } accept
meta l4proto { icmp, ipv6-icmp } accept

could be the ones that would allow the type 3, code 3 ICMP reply in. Particularly the “established, related” ought to cover majority of packets (the existing connections) and leave only the new connection attempts for the later rules.