git: 0a35e0afef28 - main - pf: allow unspecified addressed for certain MLD messages

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Mon, 07 Sep 2026 08:38:08 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=0a35e0afef2860a0a96e30e682f6782a323a82a4

commit 0a35e0afef2860a0a96e30e682f6782a323a82a4
Author:     Kristof Provost <kp@FreeBSD.org>
AuthorDate: 2026-09-02 17:03:44 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2026-09-07 08:37:46 +0000

    pf: allow unspecified addressed for certain MLD messages
    
    As per RFC 3590 MLD Report and Done messages are permitted to use the
    unspecified address as a source address (e.g. during duplicate address
    detection for the first IPv6 address). Allow this, but only this.
    
    Reported by:    Alexander Leidinger <Alexander@Leidinger.net>
    Reviewed by:    bms
    See also:       OpenBSD, sashan <sashan@openbsd.org>, 60036e8507
    Sponsored by:   Rubicon Communications, LLC ("Netgate")
    Differential Revision:  https://reviews.freebsd.org/D59334
---
 sys/netpfil/pf/pf.c         | 10 +++++++-
 tests/sys/netpfil/pf/mld.py | 57 +++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 66 insertions(+), 1 deletion(-)

diff --git a/sys/netpfil/pf/pf.c b/sys/netpfil/pf/pf.c
index 04e919639ddb..322b10c3fce6 100644
--- a/sys/netpfil/pf/pf.c
+++ b/sys/netpfil/pf/pf.c
@@ -11085,9 +11085,17 @@ pf_walk_header6(struct pf_pdesc *pd, struct ip6_hdr *h, u_short *reason)
 				 * local source address.  If either one is
 				 * missing then MLD message is invalid and
 				 * should be discarded.
+				 * RFC 3590 clarifies that during initial
+				 * duplicate address detection nodes may not
+				 * have an address, so are permitted to use
+				 * the unspecified address, but only for Report
+				 * and Done messages.
 				 */
 				if ((h->ip6_hlim != 1) ||
-				    !IN6_IS_ADDR_LINKLOCAL(&h->ip6_src)) {
+				    (!IN6_IS_ADDR_LINKLOCAL(&h->ip6_src) &&
+				     icmp6.icmp6_type == MLD_LISTENER_QUERY) ||
+				    (!IN6_IS_ADDR_LINKLOCAL(&h->ip6_src) &&
+				    !IN6_IS_ADDR_UNSPECIFIED(&h->ip6_src))) {
 					DPFPRINTF(PF_DEBUG_MISC, "Invalid MLD");
 					REASON_SET(reason, PFRES_IPOPTIONS);
 					return (PF_DROP);
diff --git a/tests/sys/netpfil/pf/mld.py b/tests/sys/netpfil/pf/mld.py
index b3ef6c21b3de..ab644d3eed4c 100644
--- a/tests/sys/netpfil/pf/mld.py
+++ b/tests/sys/netpfil/pf/mld.py
@@ -25,6 +25,7 @@
 # SUCH DAMAGE.
 
 import pytest
+import time
 from utils import DelayedSend
 from atf_python.sys.net.tools import ToolsHelper
 from atf_python.sys.net.vnet import VnetTestTemplate
@@ -45,6 +46,11 @@ class TestMLD(VnetTestTemplate):
             ])
         ToolsHelper.print_output("/sbin/pfctl -x loud")
 
+        while True:
+            cmd = self.wait_object(vnet.pipe)
+            result = ToolsHelper.get_output(cmd)
+            vnet.pipe.send(result)
+
     def find_mld_reply(self, pkt, ifname):
         pkt.show()
         s = DelayedSend(pkt, ifname)
@@ -88,3 +94,54 @@ class TestMLD(VnetTestTemplate):
         # Check if we logged dropping the MLD paacket
         dmesg = ToolsHelper.get_output("/sbin/dmesg")
         assert dmesg.find("Invalid MLD") != -1
+
+    @pytest.mark.require_user("root")
+    @pytest.mark.require_progs(["scapy"])
+    def test_unspec(self):
+        """Verify that we allow MLD packets from the unspecifed address"""
+        pf_pipe = self.vnet_map["vnet2"].pipe
+        ifname = self.vnet.iface_alias_map["if1"].name
+        ToolsHelper.print_output("/sbin/ifconfig")
+
+        # Import in the correct vnet, so at to not confuse Scapy
+        import scapy.all as sp
+        import scapy.contrib as sc
+        import scapy.contrib.igmp
+        self.sp = sp
+        self.sc = sc
+
+        # MLD packets with an incorrect hop limit get dropped.
+        pkt = sp.Ether() \
+            / sp.IPv6(src="::", dst="ff02::1", hlim=1) \
+            / sp.IPv6ExtHdrHopByHop(options=[ \
+                sp.RouterAlert(value=0) \
+                ]) \
+            / sp.ICMPv6MLReport()
+        # Send the packet, there's no reply to a report
+        pkt.show()
+        sp.sendp(pkt, iface=ifname)
+
+        time.sleep(1)
+
+        # We should not have logged a drop of an MLD packet
+        pf_pipe.send("/sbin/pfctl -sa")
+        out = self.wait_object(pf_pipe)
+        print("out %s" % out)
+        assert out.find("ip-option                              0") != -1
+
+        # However, we do still drop queries from the unspecified address
+        pkt = sp.Ether() \
+            / sp.IPv6(src="::", dst="ff02::1", hlim=1) \
+            / sp.IPv6ExtHdrHopByHop(options=[ \
+                sp.RouterAlert(value=0) \
+                ]) \
+            / sp.ICMPv6MLQuery()
+        # Send the packet, there's no reply to a report
+        sp.sendp(pkt, iface=ifname)
+
+        time.sleep(1)
+
+        pf_pipe.send("/sbin/pfctl -sa")
+        out = self.wait_object(pf_pipe)
+        print("out %s" % out)
+        assert out.find("ip-option                              1") != -1