TC ingress filters

tcfilter and luci-app-tcfilter manage persistent tc filter … ingress rules through UCI, with a LuCI front-end and a live status view. Their purpose is to drive hardware tc-flower offload (for example the Realtek DSA PIE engine) on targets where no higher-level configuration layer for it exists.

Snapshot only. The kernel-side tc-flower offload and the tcfilter package are in OpenWrt main and the snapshot feeds. They are not in any stable release (23.05, 24.10) and are not planned for backport. luci-app-tcfilter reaches the feed once openwrt/luci#9004 is merged; until then see below.

Upstream history:

Discussion and testing feedback: forum thread.

tcfilter does not model the flower match/action fields. The match and the action are written verbatim as tc syntax in option spec; the package owns the lifecycle:

  • one config rule per filter — device, pref, spec, enabled, optional cosmetic label
  • pref is mandatory — it is how a rule is removed again
  • rules are applied at boot, re-applied on ifup and on netdev add (hotplug), removed on stop
  • a procd reload trigger re-applies on config changes, so LuCI Save & Apply and uci commit tcfilter && reload_config take effect on their own
  • installed (device, pref) pairs are tracked in /var/run/tcfilter.state, so a rule deleted from the config is still torn down on the next reload
  • the clsact qdisc is added if missing but never removed (other users may share it); only the individual filters are deleted

luci-app-tcfilter adds Network → TC Filters: a grid to edit the rules plus a status table below it, polled every 5 s, parsing tc -s -j filter show … ingress — protocol, match, offload flag (skip_sw / skip_hw), in_hw, action, packet count, label.

Snapshot images already contain the kernel-side offload. The packages come from the snapshot feeds — installing the LuCI app pulls in tcfilter and tc-full:

apk update
apk add luci-app-tcfilter

Both packages are PKGARCH:=all. tcfilter depends on tc-full (the full iproute2 tc; the BusyBox tc can do neither flower nor JSON output) and, on offload targets, kmod-sched-flower.

While openwrt/luci#9004 is in review, luci-app-tcfilter is not in the feed yet. Install tcfilter on its own —

apk update
apk add tcfilter

— which already provides the UCI config, the init script and the hotplug integration; the LuCI GUI is optional. Pre-built luci-app-tcfilter packages for testing, including for realtek/rtl930x, are linked from the forum thread; alternatively build #9004 yourself.

/etc/config/tcfilter:

config tcfilter 'global'
	option enabled '1'

config rule
	option label   'Drop-mDNS (IPv4)'
	option device  'lan1'
	option pref    '49154'
	option spec    'protocol ip flower ip_proto udp dst_port 5353 skip_sw action drop'
	option enabled '1'

spec is everything that would follow

tc filter add dev <device> ingress pref <pref>

i.e. the optional protocol, the filter kind and its match, and the action. Use skip_sw so a match the hardware cannot offload fails loudly instead of silently installing in software.

The shipped config carries a few disabled examples (AVM FRITZ!Box powerline discovery, mDNS) — set device, flip enabled to 1.

Option Required Description
device yes network device the filter is attached to
pref yes tc preference number; how the rule is identified and removed
spec yes the match + action, verbatim tc syntax
enabled no (default 1) apply this rule
label no cosmetic; shown in log messages and the LuCI view
/etc/init.d/tcfilter start | stop | reload | show
/etc/init.d/tcfilter reapply_dev lan1

On the Realtek DSA target a flower … skip_sw ingress rule is programmed into the switch PIE (Packet Inspection Engine) TCAM. The driver support and the fixes that made this path usable are in OpenWrt main (pull requests in the box above) and reached snapshots during the 2026-09 cycle. Verified on a Zyxel XGS1210-12 (RTL9302C):

  • per-rule tc -s hardware packet counters work for every offloaded rule, not only the one that lands on PIE rule id 0
  • dst_port / src_port matching offloads precisely — previously a dst_port 5353 rule silently degraded to “drop all UDP
  • source / destination MAC matching offloads precisely — previously a zero-value MAC match (for example dst_mac 00:00:00:00:00:00) could degrade to “match every frame”
  • generic EtherType matching, ARP / IPv4 / IPv6 fast paths, drop / trap / redirect actions, per-port binding via the source-port mask

Combining an L3 address match with an L4 port match in one offloaded rule is rejected (-EOPNOTSUPP) on this hardware — the ingress PIE blocks do not carry a template with both fields. A plain dst_port match works.

action vlan push / action vlan pop are rejected (-EOPNOTSUPP) for offloaded rules — the rtl930x PIE VID-rewrite encoding is broken (issue #25048) — so such a rule can only be installed in software (without skip_sw).

  • ingress / clsact only
  • no dry-run validation — an invalid spec is reported via logread only
  • the free-form spec is passed to tc by word-split (no shell); anyone who can edit the config can install redirect / mirror rules
  • Last modified: 2026/09/11 00:48
  • by mab-wien1