Nftables experiments: ICMPv6, Hop-by-Hop Options header

I experimented a little with nftables, even though it's not clear whether it will ever completely replace iptables, especially after the news about bpfilter, but it was an interesting exercise anyways.

After converting some iptables rules, I looked at how others were using nftables to write a more idiomatic configuration; I found some ruleset for a host firewall, and I tried to add them to my configuration.

When testing those rules I found that some ICMPv6 packets were not being matched as I would have expected.

The problem was about this rule:

# Allow multicast listener discovery on link-local addresses.
ip6 nexthdr ipv6-icmp icmpv6 type {
    mld-listener-query,
    mld-listener-report,
    mld-listener-reduction
} ip6 saddr fe80::/10 accept

Surprisingly, it was not sufficient to cover Multicast Listener Discovery (RFC2710), in fact the fall-back logs kept showing:

[ 1029.326686] [INPUT]: IN=enp0s10 OUT= MAC=...
                        SRC=fe80:0000:0000:0000:0000:0000:0000:0001
                        DST=ff02:0000:0000:0000:0000:0000:0000:0001
                        LEN=76 TC=0 HOPLIMIT=1 FLOWLBL=0
                        PROTO=ICMPv6 TYPE=130 CODE=0

Where PROTO=ICMPv6 TYPE=130 means mld-listener-query.

When I enabled tracing:

ip6 saddr fe80::1 meta nftrace set 1

The packet was identified by the following match:

trace id 6ef0d41b inet filter input packet: iif "enp0s10" ether saddr ... ether daddr ... ip6 saddr fe80::1 ip6 daddr ff02::1 ip6 dscp cs0 ip6 ecn not-ect ip6 hoplimit 1 ip6 flowlabel 0 ip6 nexthdr ip ip6 length 36 ...

In particular the ip6 nexthdr ip part caught my attention.

I then proceeded to capture the traffic with tshark, using a capture filter:

$ sudo tshark -i enp0s10 -f "icmp6" -w /tmp/ICMPv6.pcap

The decoded dump of the packet showed all the needed information:

  Internet Protocol Version 6, Src: fe80::1, Dst: ff02::1
      0110 .... = Version: 6
      ...
      Next Header: IPv6 Hop-by-Hop Option (0)
      Hop Limit: 1
      Source: fe80::1
      Destination: ff02::1
      ...
      IPv6 Hop-by-Hop Option
          Next Header: ICMPv6 (58)
          ...
  Internet Control Message Protocol v6
      Type: Multicast Listener Query (130)
      ...

That confirmed that the packet was standard compliant, quoting RFC2710:

MLD message types are a subset of the set of ICMPv6 messages, and MLD messages are identified in IPv6 packets by a preceding Next Header value of 58. All MLD messages described in this document are sent with a link-local IPv6 Source Address, an IPv6 Hop Limit of 1, and an IPv6 Router Alert option [RTR-ALERT] in a Hop-by-Hop Options header.

So the problem had to be in the nftables rule.

I figured that the issue could be about the Hop-by-Hop Options header.

I looked in /etc/protocols and I saw that:

  ip		0	IP			# internet protocol, pseudo protocol number
  hopopt	0	HOPOPT		# IPv6 Hop-by-Hop Option [RFC1883]

So, in this case, the meaning of ip6 nexthdr ip in the nftables trace output is rather ip6 nexthdr hopopt.

After that, I double checked the nftables manual and it says:

Caution when using ip6 nexthdr, the value only refers to the next header, i.e. ip6 nexthdr tcp will only match if the ipv6 packet does not contain any extension headers.

Basically the problem with my rule was that:

  ip6 nexthdr ipv6-icmp icmpv6 type {
      mld-listener-query,
      ...

was not matching the packet because between the IPv6 header and the ICMPv6 header there was another extension header, the Hop-by-Hop Options header.

Skipping all extension headers with the following rule would work:

  meta l4proto ipv6-icmp icmpv6 type {
      mld-listener-query,
      ...

But I settled for a more explicit rule which should document better what is going on:

  hbh nexthdr ipv6-icmp icmpv6 type {
      mld-listener-query,
      ...

A rule to represent the whole headers chain would be valid too, but it would be unnecessarily verbose, I am pasting it just for reference:

  ip6 nexthdr hopopt hbh nexthdr ipv6-icmp icmpv6 type {
      mld-listener-query,
      ...

BTW, some other great resources about nftables are:

Side notes

I like the nftables syntax, especially the nested form.

Unfortunately some projects still have a hard dependency on iptables, one example is the Virtual Networking mechanism in libvirt. This means that I won't be able to use nftables on my workstation just yet.

I uploaded my nftables ruleset for a host firewall. Even if I won't use nftables, the experience surely helped to improve my iptables setup.


CommentsSyndicate content

Why not just icmpv6 type {

abouvier's picture

Why not just icmpv6 type { mld-listener-query, ... } accept?

I didn't try that mainly

ao2's picture

I didn't try that mainly because I was still influenced by iptables, and in iptables --icmpv6-type is an option only available if you specify the protocol first.

Have you tried the short syntax you suggested? Semantically it should be equivalent to meta l4proto ipv6-icmp icmpv6 type { ... }.

Anyways, even if your rule matched standard packets I think that a tighter rule still makes sense: it communicates something more about what it intends to match and can be a first filter against invalid packets.

For instance, I am thinking about a node sending a mld-listener-query type message without a hop-by-hop header first.

Ciao, Antonio

Yes it works. But I use a

abouvier's picture

Yes it works. But I use a protocol-based "dispatching system", so when a packet reach this rule, it is inevitably an icmpv6 (and a valid) packet.

table inet filter {
        chain input {
                type filter hook input priority 0; policy drop;
                ct state { established, related } accept
                ct state invalid drop
                iif "lo" accept
                meta l4proto vmap { icmp : jump icmp-chain, tcp : jump tcp-chain, udp : jump udp-chain, ipv6-icmp : jump icmpv6-chain }
                reject
        }
        # ...
        chain icmpv6-chain {
                icmpv6 type echo-request accept
                icmpv6 type { mld-listener-query, ... } ip6 saddr fe80::/10 accept
        }
}

Ah, thanks for the

ao2's picture

Ah, thanks for the example.

So an icmpv6 type rule can be also used alone as a “root” rule, good to know.

Your ruleset seems to accept valid icmpv6 packets indeed, and valid MLD packets too.

However, would it also accept packets which are valid icmpv6 but invalid MLD packets? Forged without the Hop-by-Hop extension header? I do not know.

It probably does not matter as such validation would happen at lower levels anyway after the firewall, it was just a curiosity.

Ciao, Antonio

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account associated with the e-mail address you provide, it will be used to display your avatar.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

More information about formatting options

CAPTCHA
This question is for testing whether you are a human visitor and to prevent automated spam submissions.
a
5
v
Y
g
P
Enter the code without spaces.