Log Matching Packets with tc's xt iptables Action

Logging one class of packet without writing a whole iptables rule is exactly what the tc xt action is for. You will attach a tc filter to an interface's ingress path and use the xt action to send matching packets to the iptables LOG target. The result is a kernel log message for each selected packet, without creating a separate iptables rule. Allow about fifteen minutes, including time to generate one test packet and inspect the host logs.

This guide follows the installed iproute2 6.1.0 command and its tc-xt(8) manual page. You need the iproute2 and iptables user-space tools, a loaded kernel target for LOG, and root privileges for the commands that change traffic control. Replace IFACE with the interface that really receives the traffic. Do not test this on a production interface until you have a logging-rate plan.

1. Check the installed interface

First confirm the binary and package version. These are ordinary read-only commands:

$ command -v tc
/usr/sbin/tc
$ tc -V
tc utility, iproute2-6.1.0, libbpf 1.3.0
$ dpkg-query -W -f='${Package} ${Version}\n' iproute2
iproute2 6.1.0-1ubuntu6.4

The xt action is not a general-purpose packet classifier. A filter decides which packets match, then action xt -j TARGET asks the iptables target named by TARGET to handle those packets. The target-specific arguments follow it. Here the target is LOG, and --log-prefix makes the messages findable.

Checkpoint: verify that the target is available before changing traffic control:

$ iptables -j LOG --help
... LOG target options ...

The help text varies between iptables back ends. The useful result is that the command accepts the LOG target. If it reports that the target is unknown, stop and resolve the iptables or kernel-module problem first.

2. Inspect the current ingress setup

Ingress traffic is traffic arriving at the interface. Inspect its existing qdisc and filters before making a change:

$ sudo tc qdisc show dev IFACE
$ sudo tc filter show dev IFACE parent ffff:

An ingress qdisc is normally shown with handle ffff:. If an ingress qdisc or filters already exist, record them. This example adds another filter with priority 100; reusing a priority already occupied by a rule can make the result confusing or fail. Pick a priority that is unused on your host.

Do not run the following commands against an interface name you have not checked. A filter on the wrong interface can look like a broken test, while a filter on a busy interface can create unexpected log volume.

3. Add a narrowly scoped ICMP filter

The following example matches IPv4 ICMP echo replies, whose ICMP type is 0, and passes them to LOG. It creates the ingress qdisc if one is not already present:

$ sudo tc qdisc add dev IFACE ingress
$ sudo tc filter add dev IFACE parent ffff: protocol ip pref 100 u32 \
    match ip protocol 1 0xff \
    match ip icmp_type 0 0xff \
    action xt -j LOG --log-prefix TC_ICMP_REPLY:

This is a state-changing and potentially service-disrupting step. The first command fails if the interface already has an ingress qdisc; that is safer than silently replacing one. If it fails with a message that an ingress qdisc already exists, keep it and run only the filter command after checking its existing rules.

The u32 expressions are the classifier, not options to xt. Protocol number 1 means ICMP for IPv4, and the mask 0xff says to compare all eight bits. The action then invokes the iptables target. The prefix is deliberately short because kernel log messages are limited and a long prefix makes log review harder.

Checkpoint: inspect the rule you just installed:

$ sudo tc filter show dev IFACE parent ffff:
filter protocol ip pref 100 u32
filter protocol ip pref 100 u32 fh 800::800 order 2048 key ht 800 bkt 0 flowid :1
  match 00000001/000000ff at 8
  match 00000000/000000ff at 20
        action order 1: xt verdict continue
         index 1 ref 1 bind 1 installed
          target LOG ...

Exact formatting differs between iproute2 releases and the kernel. Confirm that the displayed filter has priority 100 and contains an xt action. If the add command failed, do not interpret an old rule as the new one.

4. Generate one safe test packet

Generate an ICMP echo request from another host to the address on IFACE, then inspect the receiving host's kernel log. A reply must return through the interface for the filter to match it:

$ ping -c 1 TARGET_ADDRESS
1 packets transmitted, 1 received, 0% packet loss

Use your system's normal read-only log viewer. For a systemd host, search the kernel journal:

$ sudo journalctl -k --since '2 minutes ago' | grep 'TC_ICMP_REPLY:'
Sep 27 12:00:00 host kernel: TC_ICMP_REPLY:IN=IFACE ...

The timestamp, host name and fields after the prefix vary. No matching line usually means that the reply did not arrive on this interface, the packet was not IPv4 ICMP type 0, the filter was not installed where expected, or the target could not log. Check those in that order. A successful tc command only proves that the rule was accepted; it does not prove that traffic has matched.

5. Understand the safety boundary

LOG records packets; it does not block, redirect or accept them. The tc-xt(8) example uses this action to make the kernel log a matching packet. That does not make the filter a firewall policy. If you need enforcement, design and test an explicit action or firewall rule separately.

Logging every packet on a busy interface can consume disk, CPU and operator attention. Keep the classifier narrow, use a distinctive prefix, and remove the rule after the test. Do not use a broad match such as all IPv4 traffic merely to see whether the feature works. If logs are security-sensitive on your system, treat the prefix and captured packet metadata as operational data subject to your normal access controls.

6. Remove only this test rule

Delete the filter by its interface, ingress parent and priority:

$ sudo tc filter del dev IFACE parent ffff: protocol ip pref 100
$ sudo tc filter show dev IFACE parent ffff:

The second command should no longer show the priority 100 rule. If you used priority 100 for another filter, stop and identify the exact rule before deleting anything. If you created the ingress qdisc solely for this test and the inspection shows no other ingress filters, you can remove the qdisc:

$ sudo tc qdisc del dev IFACE ingress

That last command is destructive to the whole ingress qdisc on the interface. It removes other ingress filters attached there, so do not run it just because the test filter has gone. If the interface already had an ingress qdisc, leave it in place.

7. Diagnose common failures

An error about an existing qdisc means the setup command reached a pre-existing ingress configuration. Inspect it and add only a filter if that is appropriate. An error about an unknown xt action or target usually indicates missing iproute2 action support, iptables target support or a kernel module; it is not fixed by changing the match masks.

If the filter is visible but no log appears, verify the packet direction and protocol first. The example sees packets arriving at IFACE, not replies sent out of it. Then check the kernel journal without assuming that every distribution routes kernel messages to the same file. If the prefix appears too often, remove the rule immediately and review the filter before testing again.

Done means