Chrome’s Source-Specific Multicast feature for Direct Sockets picked up its
third LGTM on 2026-10-21, eight days after it was already shipping enabled
by default in Chrome 158 — an unusual ordering that says something about how
the Chromium feature pipeline handles a feature that gets implemented before
its intent process finishes. Meanwhile an open pull request against the WICG
Direct Sockets specification documents a dozen concrete places where the
shipped Chromium implementation and the spec text disagree, most of them
about which DOMException a caller actually gets back.
This post covers both: the intent thread’s approval path, and the spec-vs-
implementation divergence that is still open and unmerged as of this writing.
It also has a note on what this means for a browser-based ADB client trying to
use multicast for device discovery, which is the reason webadb.online pays
attention to this API at all.
The intent thread and its three LGTMs
The intent to ship for Source-Specific Multicast (SSM) was posted to blink-dev
on 2026-10-14 at 19:43 PDT, generated by Chrome Platform Status on behalf of
the feature owner. The thread is
Intent to Ship: Source Specific Multicast for Direct Sockets API,
and the feature’s chromestatus entry is
6208452397498368.
The reply sequence is short and worth reading in full, because it is an
example of the Chromium review process being asked a question it is not
usually asked, and answering it.
2026-10-19, 13:57 PDT — Reilly Grant (Chrome, IWA owner):
IWA OWNER LGTM
This is an extension of an extension of an existing IWA-only API. The
previous justification for why multicast would create a privacy issues for
the web platform (see also threads on net-dev@) still applies.
2026-10-19, 15:48 PDT — Alex Russell (Chrome, API owner for the broader
web platform):
What’s the developer benefit for SSM? Is anyone asking for this?
That question is the interesting one. Blink intents require three LGTMs from
API owners before implementation can proceed, and a feature that is already
in the tree and already enabled by default does not obviously need to survive
that process — the implementation is a fact by then, and the intent is
formality. Asking “is anyone asking for this” puts the burden back on the
requester, which is the reviewer’s job.
2026-10-20, 06:29 PDT — Omar Ramadan (feature owner, Blockcast):
The benefit is that SSM makes the multicast API usable beyond a single LAN.
Multicast that crosses network boundaries is SSM in practice:
- RFC 8815 deprecates inter-domain ASM.
- ISP and enterprise deployments carry channels in the SSM ranges (232/8,
FF3x::/32), where routers only forward source-specific joins (RFC 4607).- AMT relays (RFC 7450) serve (S,G) channels.
That is the TreeDN model (RFC 9706): native multicast where the network
supports it and AMT relays everywhere else.Who’s asking:
- My company, Blockcast. We built that workaround, and this intent lets us
drop it on native networks.
2026-10-21, 08:37 PDT — Alex Russell: Thanks Omar, LGTM1
2026-10-21, 09:38 PDT — Chris Harrelson: LGTM2
2026-10-21, 08:24 PDT — Mike Taylor: LGTM3
Three LGTMs inside a single morning, one business day after the requester’s
answer. The intent is approved.
The answer itself is technically substantial, and it is worth reading for the
specific claim it makes about ASM’s limits. The argument is not “multicast is
useful” — that was already settled when base multicast shipped. It is that
inter-domain multicast is SSM in practice, because that is what the
routing infrastructure carries:
- RFC 8815 deprecates Any-Source Multicast for inter-domain use.
- RFC 4607 defines SSM, and ISP and enterprise networks carry their
channels in the SSM address ranges — 232.0.0.0/8 for IPv4,ff3x::/32for
IPv6 — where routers only forward source-specific joins. - RFC 7450’s AMT (AMT Relay) serves
(S,G)channels. - RFC 9706 defines TreeDN, the combination.
The practical consequence for a browser application is the punchline of the
whole thread. An ASM-only client cannot receive those channels off its own
LAN. Its only workaround is to write an AMT gateway in JavaScript over
unicast UDP, which requires a relay and throws away native delivery
everywhere the network would have supported it. The Blockcast Multicast
Gateway (BEACON) Chrome extension is exactly that workaround, so the feature
owner had an existing implementation to point at.
Harald Alvestrand had made the same argument back in November 2025 in
WICG/direct-sockets#80,
which closed the same month the spec PR landed:
Basically Any-Source Multicast with a scope larger than your local network
is a high-powered DDOS vector, not something that it’s safe to build
services on, so I heartily recommend adding support for Single-Source
Multicast.
The intent also notes the precedent: Node.js’s dgram module has hadaddSourceSpecificMembership / dropSourceSpecificMembership for years.
The order is strange on purpose
Here is the part that reads like a bug and is not. The chromestatus entry
records the feature as:
1 | browsers.chrome.status: "Enabled by default" |
Chrome 158 branched on 2026-10-21 — the same day the third LGTM landed. The
feature was therefore in the tree and enabled by default before its
intent process concluded. That is not how the pipeline is supposed to work;
normally the intent comes first and the implementation follows.
What happened is a replay of the sequence this blog recorded on 2026-10-12:
commit3076e5f5
landed the SSM implementation in the network-service and Blink layers, then
seven months later was reverted —
Reason for revert: Suspicious about causing test failures of
ChromeDirectSocketsUdpIsolatedWebAppMulticastTest.multicastJoinGroupSSMSameGroupDifferentSources
at ci.chromium.org/ui/p/chromium/builders/ci/mac15-x64-rel-tests/15107.
— because of a flaky macOS test, not because of a design objection. The
implementation came back, this time under the two-week release cadence that
Chrome adopted in mid-2026, and shipped in 158. The intent process then had
to catch up to a shipped reality and confirm that shipping it was still the
right call.
That is the honest framing: the LGTMs are not pre-approval of a design, they
are a ratification of a shipped one. The “is anyone asking for this”
question from Alex Russell is what makes it a ratification rather than a
rubber stamp — the answer had to still be good in October 2026, not just in
the November 2025 prototype.
The spec and the implementation do not agree
The second piece of news is quieter and, for anyone writing code against the
API today, more immediately useful.
WICG/direct-sockets#86 —
“Multicast: disallow ASM/SSM mixing per group, validate leaveGroup(),
handle close” — was opened 2026-10-14 by kkroo and is still open. Its
description is a list of five concrete divergences between the spec text and
what Chromium actually does:
joinGroup()throws anInvalidStateErrorwhen the socket already has,
or is joining, a membership of the other kind for the samegroupAddress. That means an SSM join while an ASM membership exists,
or an ASM join while SSM memberships exist. The explainer already states
this rule (“Mixing ASM and SSM memberships for the same group address is
not allowed”).leaveGroup()validatesgroupAddress(multicast) andoptions.sourceAddress(unicast, not loopback or unspecified, same IP
version) the same wayjoinGroup()does. It throws aTypeErrorbefore
theInvalidStateErrormembership check.- Both methods check for a closed socket first, before validating their
arguments.leaveGroup()throws anInvalidStateErrorwhile the join is still
pending, because[[joinedGroups]]only holds completed joins.- Closing the socket rejects pending
joinGroup()andleaveGroup()
promises with anAbortError, andjoinedGroupsis empty afterwards
(Chromium’sMulticastController::OnCloseOrAbort()).
Point 5 is the one with real architectural weight, and it is worth spelling
out. The spec previously had no defined way to cancel a pendingjoinGroup(). A joinGroup() call issues a setsockopt(MCAST_JOIN_GROUP)
against the network service; if the socket is closed while that is in flight,
the promise had no defined settlement path. Chromium’s implementation
answers it with AbortError, and the spec PR writes that down.
This is not a theoretical concern. The whole reason SSM exists is thatMCAST_JOIN_SOURCE_GROUP is a privileged operation that a browser’s
network-service sandbox has to allow explicitly. The original implementation
landed without those setsockopt codes in the BPF allowlist, and every SSM
join attempt hit the sandbox’s CrashSIGSYSSockopt() and killed the process:
Root cause:
MCAST_JOIN_SOURCE_GROUP/MCAST_LEAVE_SOURCE_GROUPwere
not in the BPF setsockopt allowlist for the network service sandbox. Any SSM
setsockopt call hitCrashSIGSYSSockopt(), killing the process.Fix: crrev.com/c/7641869 —
MCAST_JOIN_SOURCE_GROUPandMCAST_LEAVE_SOURCE_GROUPadded to both the IPv4 and IPv6 setsockopt case
lists inRestrictSetSockoptForNetworkService().
So a join can fail slowly — after a network round trip — and the failure mode
has to be specified, because “the socket closed while your join was in flight”
is a completely ordinary thing for an application to do during teardown.
The shape of the API
For reference, the API surface is small. MulticastController is obtained
from the multicastController member of the dictionary returned by a boundUDPSocket‘s opened promise, and it exposes exactly three things:
1 | // The socket must be in bound mode. |
The joinedGroups return type is a deliberate feature-detection mechanism,
and it is the reason the intent could claim the feature is low-risk:
Low.
sourceAddressis an optional dictionary member: code that doesn’t
pass it behaves exactly as before, andjoinedGroupsstill returns strings
for ASM memberships. Where SSM is unavailable (older Chrome, or the feature
turned off),sourceAddressis ignored andjoinGroup()makes an ASM join;
apps can detect this becausejoinedGroupsthen lists a string, not aMulticastMembership.
That is a well-constructed backwards-compatibility story. An application
that wants SSM can call joinGroup(group, { sourceAddress }) unconditionally
and then inspect joinedGroups to find out whether it actually got a
source-filtered membership or silently degraded to ASM. No feature detection
branch at the call site, no version sniffing.
The mismatches that are being deferred
The PR is honest about what it does not fix. These gaps predate SSM and
apply to ASM as well:
- The spec compares
groupAddressandsourceAddressas strings.
Chromium compares the parsed IP addresses, andjoinedGroupsreturns their
canonical form (FF02::1is listed asff02::1). - The spec orders
joinedGroupsby join time. Chromium returns hash-map
order. - The spec rejects every OS failure with
NetworkError. Chromium maps some
errors toDataError,NotSupportedError,InvalidAccessErrororInvalidStateError.
That third bullet is the one that will bite application code. A caller that
writes catch (e) { if (e.name === "NetworkError") { /* retry */ } } is
wrong against the shipped implementation for at least four distinct error
classes. A robust caller has to treat the DOMException name as an
open set.
The PR also notes that the spec already requires rejecting a loopbacksourceAddress, and that Chromium aligns with that in CL
8582708
— a loopback source would defeat the entire security model of SSM, since
RFC 4607’s model assumes a routable source.
What this means for webadb.online
Direct impact on webadb.online today: none, and that is by design.
webadb.online reaches devices over WebUSB, not over Direct Sockets.
The Direct Sockets API is gated behind Isolated Web Apps, and IWAs are
ChromeOS-only for now — an IWA cannot be installed on the Android phone that
is plugged into the user’s laptop, and an IWA cannot ask for a WebUSB device
the way a normal page can. The two mechanisms are complementary rather than
substitutable, and webadb.online uses the one that works in a regular tab on
any platform.
But the reason to keep tracking this API is specific: Direct Sockets
multicast is the only browser-reachable mechanism for mDNS-style device
discovery, and mDNS discovery is what a Wi-Fi-ADB workflow needs. Today,
webadb.online’s Wi-Fi ADB path requires the user to type the device’s IP
address and port by hand. If an IWA can join a multicast group and listen
for mDNS responses, discovery becomes automatic.
The shape of that future is now much clearer, and it comes with the
caveats this post has been documenting:
- SSM is the model that actually works off-LAN. The intent thread’s
central argument — that inter-domain multicast is SSM in practice
(RFC 8815, RFC 4607, RFC 7450, RFC 9706) — is exactly the argument for
an ADB-over-network feature that has to work on a corporate Wi-Fi network
rather than only on a home LAN. An ASM-only client on an enterprise
network would receive nothing at all, because enterprise switches
commonly filter or drop ASM joins outside the local scope. - The API degrades gracefully, but the detection is non-obvious. Because
sourceAddressis silently ignored where SSM is unavailable, thejoinedGroupsreturn-type difference is the only reliable signal. Any
webadb code that adopted this must check the shape of the returned
value, not just its presence. - The error semantics are still in flux. PR #86 is open. The
AbortError
on socket close, theTypeError-before-InvalidStateErrorordering inleaveGroup(), and theInvalidStateErrorfor ASM/SSM mixing are all
documented-but-unmerged. Writing against the spec today means writing
against a spec that does not yet match Chrome 158.
The third point is the general lesson, and it applies well beyond this API.
The Chromium feature pipeline can ship a feature enabled-by-default and
then run its intent process, and the specification can lag the
implementation by months. When you are writing against a browser API that
just shipped, the implementation is the ground truth and the spec is a
roadmap.
One closing note on the upstream ADB side, for completeness: ya-webadb
itself was completely quiet this week. The repository’s main branch still
sits at 3a7d781c (2026-10-27, the scrcpy inject-key shorthands), there
were no new releases since v3.0.0-beta.4 on 2026-10-09, and no issue or
pull request in the repository was updated in the last 24 hours. scrcpy
upstream is likewise unchanged since 2026-10-08. Two posts ago this blog
covered the withdrawal of ya-webadb PR #875; that remains the most recent
movement in the library, and there is nothing new to report on it.
Timeline
For readers who want the whole arc in one place:
| Date | Event |
|---|---|
| 2025-09-23 | Explainer for multicast in Direct Sockets merged (WICG#78) |
| 2025-10-09 | Multicast added to the spec (WICG#79) |
| 2025-11-19 | SSM chromestatus entry created (6208452397498368) |
| 2025-11-26 | SSM added to the spec (WICG#81) |
| 2025-12-01 | Intent to Prototype posted to blink-dev |
| 2026-02-21 | SSM added to the explainer (WICG#83) |
| 2026-02-21 | SSM lands in Chromium (main@{1597329}) |
| 2026-07-10 | Sending to a multicast address requires the permission policy (WICG#85) |
| 2026-09-12 | Chromium commit 3076e5f5 reverts the SSM implementation over a flaky macOS test |
| 2026-10-08 | SSM relands and is cherry-picked to the 158 branch |
| 2026-10-14 | Intent to Ship posted; WICG#86 opened |
| 2026-10-19 | Reilly Grant: IWA OWNER LGTM. Alex Russell asks “is anyone asking for this?” |
| 2026-10-20 | Omar Ramadan answers; SSM implementation is in the tree for 158 |
| 2026-10-21 | Alex Russell LGTM1, Chris Harrelson LGTM2, Mike Taylor LGTM3 |
| 2026-10-21 | Chrome 158 branches |
| 2026-10-27 | chromestatus entry 6208452397498368 last updated |
| 2026-10-30 | This post |
References
- Chrome Platform Status entry —
6208452397498368 - Intent to Ship thread —
blink-dev - Spec divergence pull request —
WICG/direct-sockets#86 - Multicast explainer —
WICG/direct-sockets/docs/multicast-explainer.md - Chromium implementation —
third_party/blink/renderer/modules/direct_sockets/multicast_controller.cc - Chrome Direct Sockets documentation —
developer.chrome.com/docs/iwa/direct-sockets