I've set up a rule to match multicast packets as follows:

add rule filter_4 new_out_4 meta pkttype multicast goto multicast_out_4

filter_4 is IPv4 table, new_out4 is output chain and multicast_out_4 is a chain to handle multicast only traffic.

Here is a more complete picture of the IPv4 table excluding non-relevant portion:

#!/usr/sbin/nft -f

add table filter_4

add chain filter_4 output {
    # filter = 0
    type filter hook output priority filter; policy drop;

add chain filter_4 multicast_out_4 {
    comment "Output multicast IPV4 traffic"

add chain filter_4 new_out_4 {
    comment "New output IPv4 traffic"

# Stateful filtering

# Established IPv4 traffic
add rule filter_4 input ct state established goto established_in_4
add rule filter_4 output ct state established goto established_out_4

# Related IPv4 traffic
add rule filter_4 input ct state related goto related_in_4
add rule filter_4 output ct state related goto related_out_4

# New IPv4 traffic ( PACKET IS MATCHED HERE )
add rule filter_4 input ct state new goto new_in_4
add rule filter_4 output ct state new goto new_out_4

# Invalid IPv4 traffic
add rule filter_4 input ct state invalid log prefix "drop invalid_filter_in_4: " counter name invalid_filter_count_4 drop
add rule filter_4 output ct state invalid log prefix "drop invalid_filter_out_4: " counter name invalid_filter_count_4 drop

# Untracked IPv4 traffic
add rule filter_4 input ct state untracked log prefix "drop untracked_filter_in_4: " counter name untracked_filter_count_4 drop
add rule filter_4 output ct state untracked log prefix "drop untracked_filter_out_4: " counter name untracked_filter_count_4 drop

In the above setup new output traffic including multicast is matched by rule add rule filter_4 output ct state new goto new_out_4

Here is new_out_4 chain with only the relevant (non working) multicast rule that doesn't work:

add rule filter_4 new_out_4 meta pkttype multicast goto multicast_out_4

# Default chain action ( MULTICAST PACKET IS DROPPED HERE )
add rule filter_4 new_out_4 log prefix "drop new_out_4: " counter name new_out_filter_count_4 drop

Here is what the log says about dropped multicast packet:

drop new_out_4: IN= OUT=eth0 SRC= DST= LEN=163 TOS=0x00 PREC=0x00 TTL=255 ID=27018 DF PROTO=UDP SPT=5353 DPT=5353 LEN=143

The packet that is dropped was sent to destination address, this is multicast address, it was supposed to be matched by multicast rule in new_out_4 chain and was supposed to be processed by multicast_out_4 chain but was not.

Instead the packet was not matched and was dropped by default drop rule in new_out_4 chain above, see comment (Default chain action).

Obviously this means that the multicast rule does not work.

Why multicast rule doesn't work?


meta pkttype multicast matches destination address


System info:
Kernel: 6.5.0-0.deb12.4-amd64
had the same problem with earlier kernel 6.1

nftables: v1.0.6 (Lester Gooch #5)

1 Answer 1


Having reproduced (and completed missing parts for) the setup with a few additional entries such as:

nft insert rule filter_4 new_out_4 counter meta pkttype host counter

indeed the property meta pkttype for this skbuff is host rather than the expected multicast for an outgoing multicast packet. Note that when this keyword was introduced, it was about input, not output:

src: Add support for pkttype in meta expresion

If you want to match the pkttype field of the skbuff, you have to use the following syntax:

nft add rule ip filter input meta pkttype PACKET_TYPE

where PACKET_TYPE can be: unicast, broadcast and multicast.

Actually the direct equivalent with iptables is the pkttype match module:


This module matches the link-layer packet type.

[!] --pkt-type {unicast|broadcast|multicast}

# iptables-translate -A OUTPUT -m pkttype --pkt-type multicast
nft 'add rule ip filter OUTPUT pkttype multicast counter'

Putting all this together, when an outgoing IP (routing: layer 3) packet is created, it has not yet reached its layer 2 (link-layer) so its skbuff doesn't reflect what it might become, if it's even intended to later.

What should actually be tested is the IP address property with regard to the routing stack, rather than the packet property wih regard to Ethernet. iptables provides for this the addrtype match module:


This module matches packets based on their address type. [...]

Its translation hints what should actually be used: the fib expression:

# iptables-translate -A OUTPUT -m addrtype --dst-type MULTICAST
nft 'add rule ip filter OUTPUT fib daddr type multicast counter'


fib {saddr | daddr | mark | iif | oif} [. ...] {oif | oifname | type}

A fib expression queries the fib (forwarding information base) to obtain information such as the output interface index a particular address would use. The input is a tuple of elements that is used as input to the fib lookup functions.

There's no direct example for multicast. The nearest example is a more complex one about dropping a pacjet not intended for an address on the incoming interface, where multicast is in the 3 exceptions:

# drop packets to address not configured on incoming interface
filter prerouting fib daddr . iif type != { local, broadcast, multicast } drop

So just replace wherever used in an output (or postrouting) hook:

meta pkttype multicast


fib daddr type multicast

In an input (or prerouting) hook, while the skbuff property probably matches the IP property, to be consistent, it should also be replaced exactly the same, also with:

fib daddr type multicast

The test command below, used conjointly on an other host in the LAN (to test input) and on the host (to test output):

socat -d -d UDP4-DATAGRAM:,bind=:5555,ip-add-membership= -

will properly match fib daddr type multicast in input and output.

Important remark:

I believe above addressed the question, but note however that multicast cannot be properly tracked by Netfilter's conntrack, because it can't associate a reply using an other unicast source address to the multicast destination address of the initial query: they differ so it considers the reply to be an other (new) flow instead of associating them and considering such reply as part of the previous flow. So such kind of flow will never appear as ESTABLISHED state with conntrack or the conntrack -L command. The ruleset should be adapted for this: it can't rely only on ct state established,related kind of rules, but that's beyond the scope of this question.

  • I very much appreciate your detailed answer! I've already lost all hopes but now finally understand it into the core :) Commented Mar 9 at 17:26

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .