feat(general): drop irrelevant mDNS packets before decode#3995
Merged
Conversation
Busy LANs flood mDNS, almost all of it irrelevant to us, yet every packet was fully decoded before any relevance gate. Add a pre-decode relevance filter on the mDNS socket: consumers register the DNS names they care about, and each inbound packet is scanned (names only, RDATA skipped, compression pointers followed) for a distinctive-label footprint before paying for a full decode. - MdnsRelevanceFilter: self-contained class holding a refcounted incremental registry (O(1) per tracked name) and a names-only partial-parse matcher. Conservative superset - any uncertainty keeps the packet, so a false drop never occurs. ~3x cheaper than decode and flat in the number of footprints. - DnssdNames.filters.add now requires the names a filter accepts (or "all", which disables the filter socket-wide); SRV-target hostnames are registered incrementally as they are discovered/expired. - MdnsServer, MdnsService and CommissionableMdnsScanner declare their names. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Pull request overview
This PR introduces a pre-decode mDNS relevance gate in @matter/general so busy LAN traffic can be dropped before paying the cost of full DnsCodec.decode, and wires DNS-SD consumers to declare (and incrementally track) the names they care about to drive that gate.
Changes:
- Added
MdnsRelevanceFilterand integrated it intoMdnsSocketto drop irrelevant packets before decoding. - Updated DNS-SD name filtering APIs to require/propagate accepted names (service types / tracked hostnames) into the socket-level relevance registry.
- Added/expanded tests for the relevance pre-filter and its edge cases (compression pointers, malformed packets, incremental refcount behavior).
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/protocol/src/mdns/MdnsService.ts | Declares Matter service suffixes once and provides filterNames to drive the socket pre-filter. |
| packages/protocol/src/mdns/MdnsServer.ts | Registers responder-owned names with the socket so responder-relevant queries/answers survive pre-filtering. |
| packages/protocol/src/mdns/CommissionableMdnsScanner.ts | Updates filters.add usage to provide accepted names for pre-filtering. |
| packages/general/test/net/dns-sd/MdnsSocketTest.ts | Adds end-to-end coverage proving irrelevant packets are filtered before decode and relevant ones pass. |
| packages/general/test/net/dns-sd/MdnsRelevanceFilterTest.ts | Adds unit tests for relevance derivation, refcounting, pointer-following, and malformed-packet keep behavior. |
| packages/general/test/net/dns-sd/DnssdNamesTest.ts | Updates tests for the new filters.add(filter, names) API shape. |
| packages/general/src/net/dns-sd/ServiceDiscovery.ts | Provides accepted type name(s) when registering a discovery filter. |
| packages/general/src/net/dns-sd/MdnsSocket.ts | Wires the pre-decode relevance check into the receive path and exposes registration APIs. |
| packages/general/src/net/dns-sd/MdnsRelevanceFilter.ts | Implements names-only packet scanning and owner/refcount tracking to decide pre-decode relevance. |
| packages/general/src/net/dns-sd/index.ts | Exports MdnsRelevanceFilter. |
| packages/general/src/net/dns-sd/DnssdNames.ts | Tracks filter-declared names and discovered hostnames, publishing both to the socket relevance registry. |
| CHANGELOG.md | Documents the new optimization and related protocol-side wiring. |
…vance scan Address PR review: the names-only scan must defer to the decoder (keep) on any malformed structure, never drop. Add the missing guards: reserved label types (top bits 0b01/0b10), compression pointers into the header, a question name truncated before QTYPE/QCLASS, and an RDLENGTH running past the packet end. All are strictly more conservative (more keeps, never fewer). Fix CHANGELOG grammar. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Contributor
|
Tick the box to add this pull request to the merge queue (same as
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
On a busy LAN, mDNS is a firehose — printers, AirPlay, Chromecast, Spotify, etc. A real high-CPU capture from a Home Assistant host showed ~97% of mDNS frames are irrelevant to Matter/Thread, yet every packet was fully decoded (
DnsCodec.decode) before any relevance gate ran. That decode + GC churn per packet is what hurts under bursts, especially on constrained devices.Change
A pre-decode relevance filter at the single decode chokepoint (
MdnsSocket.#handleMessage). Consumers register the DNS names they care about; each inbound packet is scanned for a footprint before a full decode.MdnsRelevanceFilter(new, self-contained class): a refcounted incremental registry (O(1) per discovered/expired name) plus a names-only partial-parse matcher — walks the DNS name structure only, skips RDATA viardlength, follows compression pointers (backward-only, hop-bounded), folds ASCII case (RFC 4343), FNV-1a hashes each label, testsMapmembership. A footprint is a name's rightmost non-stoplist label (_tcp/_udp/local/_subskipped), so service families collapse to one label while hostnames stay distinct.DnssdNames.filters.add(filter, names)now requires the names a filter accepts (or"all", which disables the filter socket-wide — documented). Tracked SRV-target hostnames register incrementally, so operational A/AAAA answers that carry no service text still pass.MdnsServer,MdnsServiceand matterjs-server'sBorderRouterDiscovery(separate change) declare their service types.Why names-only partial parse (measured)
Benchmarked the real
DnsCodec.decodevs candidate matchers (Node 24):DnsCodec.decode(full)Operational hostnames don't fold (one footprint per tracked peer, 50–200 on a controller), so any per-footprint scan loses at scale. The partial parse is ~3× faster than decode and flat in N. The registry is incremental specifically so a discovery burst doesn't reintroduce an O(N)-per-packet cost (an event-loop concern).
Tests
MdnsRelevanceFilterTest— 32 unit tests: activation, derivation, case-insensitivity, incremental add/remove + reference counting, multi-owner, replace/"all"transitions, packet-structure walking, compression-pointer following, malformed→keep.MdnsSocketTest— end-to-end gate behavior through the socket.MdnsServer/DnssdNames/ scanner suites green: general 1202, protocol 1242. Build, format, lint clean.Follow-ups (not in this PR)
BorderRouterDiscoverymust pass[MESHCOP_TYPE_QNAME, TREL_TYPE_QNAME]tofilters.add(now a required arg).🤖 Generated with Claude Code