Page MenuHomeFreeBSD

netinet6: add per-interface MLD report suppression (ifconfig no_mld)
Changes PlannedPublic

Authored by paulo_nlink.com.br on Mon, Aug 24, 7:38 PM.
Referenced Files
F168711186: D59160.id184933.diff
Sat, Aug 29, 6:34 PM
F168647377: D59160.id.diff
Sat, Aug 29, 10:33 AM
Unknown Object (File)
Fri, Aug 28, 2:36 AM
Unknown Object (File)
Thu, Aug 27, 9:09 AM
Unknown Object (File)
Thu, Aug 27, 7:28 AM
Unknown Object (File)
Wed, Aug 26, 8:17 AM
Unknown Object (File)
Tue, Aug 25, 10:29 PM
Unknown Object (File)
Tue, Aug 25, 8:19 PM

Details

Reviewers
kp
glebius
pouria
Summary

Internet Exchange port policies commonly forbid non-unicast frames on
the peering fabric, with a narrow exception for broadcast ARP and
ICMPv6 Neighbor Discovery. MLD is ICMPv6 but is not ND, so MLD reports
fall under the prohibition.

There is currently no supported way to hold an IPv6 address on a
FreeBSD interface without emitting MLD. Solicited-node group
membership is mandatory (RFC 4291 2.7.1, RFC 4861 7.2.1) and membership
obliges reporting (RFC 3810 5). The MLD sysctls select protocol
version behaviour rather than disabling reporting, and are not
per-interface. The only remedy today is an egress packet filter, which
every operator at an exchange must rediscover independently --
typically after being notified by the exchange that they are in
violation.

Add ND6_IFF_NO_MLD, settable as "ifconfig <if> inet6 no_mld",
following the existing precedent of no_dad, no_radr and
no_prefer_iface: per-interface, opt-in, default off, deviating from an
IPv6 RFC where operational policy requires it. Per-interface rather
than a sysctl because an operator wants suppression on peering ports
and normal behaviour on LAN ports.

The gate is in mld_dispatch_packet(), through which every transmitted
MLD packet passes -- MLDv1 reports and Done via
mld_v1_transmit_report(), MLDv2 reports via mld_dispatch_queue().
Gating at dispatch rather than in the state machine leaves group state
and bookkeeping untouched: the stack believes it transmitted, which is
the behaviour a packet filter produces today and is therefore the
semantics already proven in the field. Reception is unaffected. The
check follows the access idiom established in 576ee62dd2e5.
Intentional suppression is traced via KTR_MLD and not counted as an
output error.

Bits 0x200 and 0x400 are left vacant: they were ND6_IFF_IPV6_ONLY and
ND6_IFF_IPV6_ONLY_MANUAL in released kernels, and the nd6 flag word is
visible to userland, so reusing them would cause older ifconfig
binaries to mislabel the flag.

The equivalent behaviour, implemented as an ipfw egress filter, has
run in production on AS28585 across eight IX.br localities: no MLD on
any peering VLAN, ND unaffected, all BGP sessions established, and the
exchange confirmed the traffic ceased.

Question for glebius@: with the ND context now embedded in in6_ifextra (576ee62dd2e5), a dedicated mld_ifsoftc field and an nd6 flag are equally local. I chose the nd6 flag for ifconfig consistency — happy to move it if you prefer.

Test Plan

kyua test in tests/sys/netinet6 on 16.0-CURRENT (n288360): all three cases pass (mldraw01, pr233683, and the new no_mld). The no_mld case verifies, via epair/vnet jails with an egress-only capture: the flag round-trips through ifconfig; the flagged interface emits zero MLD packets while still transmitting DAD NS; neighbor resolution and ping succeed across the link with suppression active; clearing the flag restores normal reporting.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Skipped
Unit
Tests Skipped

Event Timeline

pouria requested changes to this revision.Tue, Aug 25, 8:11 AM

Hi,

Thank you for your contribution.
I understand your concern.
I've encountered similar policies at multiple IXPs as well. (ofc not for MLD)

However, I strongly against this change.
Think about it this way

  1. Why don’t they want certain L2 traffic on an IXP switch? To prevent unnecessary traffic and etc...
  2. Why is broadcast ARP allowed in those IXPs? IPv4 can't work without it
  3. Why doesn't IPv6 have broadcast, and why does it use multicast for those purposes instead? To reduce broadcast traffic (same as 1)
  4. How did IPv6 designers limit NS/RS to certain nodes to prevent broadcast? By enforcing MLD

TL;DR: Because ND relies on MLD, and MLD is mandatory in IPv6.

This revision now requires changes to proceed.Tue, Aug 25, 8:11 AM

I'm the original author of this code. I have run into exactly the same issue you have called out, but at COMEX/CME in Chicago with IGMPv3 being seen upstream of a demarc boundary where only PIM traffic was expected. This was on Linux, and we ended up filtering outgoing IGMP traffic with iptables; ~2010.

I broadly agree with the objections raised by @pouria here. Solicited-node groups are an essential part of IPv6 link bootstrap now.

Are there reasons that filtering outgoing MLD traffic with a supported netpfil client is insufficient for your use case, as you already seemed to be doing with ipfw? If so, can you please state what these reasons are? Ease of management isn't a sufficient argument in favour here, IMHO.

We've had enough problems with downstream consumers of this code not upstreaming fixes or functional enhancements to us as it is, and this has consumed valuable cycles for project participants, regardless of their Foundation funded responsibilities or not.

You seem to be advocating additional complexity be added to MLD. Whilst placeholders were left for redirecting on-link multicast control plane traffic for both IGMP and MLD to loopback to support MANET use, which broadly overlaps with what you propose, that's an entirely different and quite specialist use case, which will probably never be implemented, dating back to CRC Canada's use of my MANET stack.

@pouria — thanks for the reasoning chain, but I believe step 4 conflates two mechanisms. What limits NS processing to the right nodes on a flat L2 is the solicited-node-to-33:33:xx MAC mapping: NICs filter in hardware, no MLD involved. MLD's role is informing snooping switches and multicast routers where to forward group traffic so they can prune instead of flooding (RFC 4541). So "ND relies on MLD" holds on fabrics whose switches prune by snooping — and IXP peering LANs are the opposite case: they flood link-local multicast, which is why their operators can and do forbid MLD (IX.br PRT, Euro-IX). Your step 2 is actually the argument: exchanges permit ARP because IPv4 can't work without it, and they permit NS/NA while forbidding MLD because, on their fabric, ND works without it. That analysis was done by the people who run the switches.

@bms — first, a clarification, because I may not have made it explicit enough in the summary: this change does not touch solicited-node groups. Joins happen normally, membership is intact, and DAD NS transmits — the regression test asserts this specifically. Only the transmission of reports (130/131/132/143 egress) is suppressed; the state machine runs as if transmission occurred, which is byte-for-byte what the egress filter produces today.

On your direct question — why netpfil is insufficient: the filter works in the steady state, and I run it in production. The gap is ordering at boot. The ruleset loads from rc after interfaces are configured, and the MLD join burst transmits the moment the first inet6 address is assigned — so the exact packets the exchange flags escape on every boot unless the operator also restructures firewall startup. An interface flag has no window: it is applied in the same ifconfig invocation that configures the address, before the joins are scheduled. That is a correctness property of the mechanism, not ease of management. Secondarily, the flag is visible state (nd6 options=...<NO_MLD>) that monitoring and audit can see, where a filter rule's intent is not discoverable from the interface.

On complexity: the patch is one flag bit and one branch in mld_dispatch_packet(), with no new state and no state-machine change. I'd also note MLIF_SILENT/MLD_SILENT_MEMBER already express per-link MLD silence in your design (set today only for !IFF_MULTICAST interfaces); I deliberately did not touch that machinery, but if you'd rather the knob drive MLIF_SILENT than gate at dispatch, I'm glad to rework it that way.

Finally — the reason this is a review and not a private patch is exactly the concern you raise about downstream consumers: I've been carrying the equivalent behaviour locally across eight IX.br localities and would rather upstream it than fork it. If the answer is that this doesn't belong in the tree, the fallback I'd propose is a documentation patch: a note in inet6(4)/ifconfig(8) that address assignment transmits MLD before any filter can be loaded, with the recommended filter recipe, so the next operator finds it before the compliance notice rather than after.

@pouria MLD's role is informing snooping switches and multicast routers where to forward group traffic so they can prune instead of flooding (RFC 4541). So "ND relies on MLD" holds on fabrics whose switches prune by snooping

Exactly. They're defeating the purpose of their design choice.

and IXP peering LANs are the opposite case: they flood link-local multicast, which is why their operators can and do forbid MLD (IX.br PRT, Euro-IX).

That's their own decision.
They want to reduce L2/flooded traffic while disabling the same exact protocol that suppose to achieve that.

Your step 2 is actually the argument: exchanges permit ARP because IPv4 can't work without it, and they permit NS/NA while forbidding MLD because, on their fabric, ND works without it. That analysis was done by the people who run the switches.

I'm not native english speaker, but I said ND relies on MLD, of course it can work without it. (RFC1925, rule 3)

I understand that some IXPs choose to suppress MLD, but we should not making it easier to violate IETF RFCs.

Only the transmission of reports (130/131/132/143 egress) is suppressed

This is actually the part that concerns me most.

As you said before, MLD exists precisely to let the switch learn multicast listener membership and avoid flooding multicast traffic.
Suppressing MLD removes that information, so an MLD-snooping switch cannot constrain solicited-node multicast to the interested ports and will fall back to flooding it.
This is the exact opposite of what they're trying to achieve and it's not comparable to ARP/IGMP because ARP simply use broadcast, therefore it's understandable to suppress IGMP in that case.

An IXP may nevertheless choose to flood link-local multicast as an operational policy.
That's their choice, but I don't think FreeBSD should accommodate that choice by adding NO_MLD to the IPv6 stack.
The fact that some IXPs have chosen a policy that defeats multicast pruning is not a good reason to violate IPv6 RFCs.

If the requirement is simply "don't let MLD packets leave this interface", that's an egress filtering policy and should be handled by the firewall, not by teaching the IPv6 stack not to generate MLD.

The gap is ordering at boot. The ruleset loads from rc after interfaces are configured, and the MLD join burst transmits the moment the first inet6 address is assigned — so the exact packets the exchange flags escape on every boot unless the operator also restructures firewall startup.

If you're worry about leaking MLD on boot and you're using IPFW, do set the net.inet.ip.fw.default_to_accept=0 tunable which is the default. (Not sure about other fw modules)

I've been carrying the equivalent behaviour locally across eight IX.br localities

If this is really an established requirement across your eight IX.br locations, I'd suggest taking that up with the operators or raising it through the appropriate NOG/RIR working groups.

@pouria — you're right that this is solvable in the firewall, and I should have checked before offering the boot-window argument. One detail worth recording: on a stock system rcorder puts netif well before rc.d/ipfw, and net.inet.ip.fw.default_to_accept reads 1 on my hosts — so with ipfw loaded from rc.conf the window is real. It closes with ipfw_load="YES" and net.inet.ip.fw.default_to_accept=0 in loader.conf, which makes the default deny active before any interface is configured. That's the correct answer to my objection, and it belongs in documentation.

Given that and @bms's position, I won't push this further. I'll withdraw the flag and propose a documentation patch for inet6(4)/ifconfig(8) instead, covering that address assignment transmits MLD immediately and the loader.conf ordering above. Thanks both — the review was genuinely useful, and I'll take the policy question to GTER/GTS as you suggested.