WebKit Bugzilla
New
Browse
Search+
Log In
×
Sign in with GitHub
or
Remember my login
Create Account
·
Forgot Password
Forgotten password account recovery
RESOLVED FIXED
312332
REGRESSION (Safari 26.3 - 26.4): RTCPeerConnection with iceTransportPolicy "relay" gathers zero ICE candidates
https://bugs.webkit.org/show_bug.cgi?id=312332
Summary
REGRESSION (Safari 26.3 - 26.4): RTCPeerConnection with iceTransportPolicy "r...
devalevd
Reported
2026-04-14 17:24:17 PDT
Created
attachment 479076
[details]
ICE gathering issue reproducing sample # Description After upgrading to Safari 26.4, `RTCPeerConnection` configured with `iceTransportPolicy: "relay"` and valid TURN server credentials fails to gather any ICE candidates. The `iceGatheringState` transitions to `"gathering"` but never reaches `"complete"`, and zero `icecandidate` events are fired. This worked correctly in Safari 26.3 and earlier. We suspect this is related to the change in Safari 26.4 ("Fixed RTCConfiguration.iceServers to be a non-optional sequence with an empty array as the default"). The spec compliance fix may have introduced a side effect where TURN relay candidate allocation silently fails. ## Steps to Reproduce I have also attached a sample HTML file to run. It includes a global TURN server URL as test URL. But apart from it, some code: 1. Create an `RTCPeerConnection` with TURN credentials and relay-only policy: ```javascript const pc = new RTCPeerConnection({ iceServers: [{ urls: ["turns:turn.example.com:443?transport=tcp"], username: "<valid-username>", credential: "<valid-password>" }], iceTransportPolicy: "relay" }); ``` 2. Add a media track (audio or video) 3. Create and set a local offer: ```javascript const offer = await pc.createOffer(); await pc.setLocalDescription(offer); ``` 4. Listen for ICE candidates: ```javascript pc.onicecandidate = (event) => { console.log("candidate:", event.candidate); }; pc.onicegatheringstatechange = () => { console.log("gathering state:", pc.iceGatheringState); }; ``` ## Expected Results - `iceGatheringState` transitions: `"new"` → `"gathering"` → `"complete"` - At least one relay `icecandidate` event fires - A null candidate event fires indicating gathering is complete ## Actual Results - `iceGatheringState` transitions to `"gathering"` and stays there indefinitely - Zero `icecandidate` events fire - Gathering never completes, causing a timeout ## Regression - **Safari 26.3:** Works correctly — relay candidates gathered within 1-2 seconds - **Safari 26.4:** Broken — zero candidates, gathering hangs indefinitely ## Impact This is a critical regression affecting production WebRTC applications. We have confirmed this across multiple users on Safari 26.4 / macOS 15.7.5 using the Amazon Chime SDK for JavaScript (v3.30.0) (
https://github.com/aws/amazon-chime-sdk-js
). Meeting connections fail 100% of the time on Safari 26.4 with a 30-second timeout, directly impacting users session joins. ## Additional Testing We also reproduced this using the official WebRTC sample at
https://webrtc.github.io/samples/src/content/peerconnection/trickle-ice/
— when configured with valid TURN servers, Safari 26.4 gathers zero relay candidates. The same TURN servers produce relay candidates on Safari 26.3, Chrome, Edge, and Firefox. This confirms the issue is in Safari's WebRTC stack and not specific to any particular application or SDK. ## Environment - Safari 26.4 on macOS 15.7.5 - Tested on stable Wi-Fi, no VPN, no corporate firewall - Works on Chrome, Edge, Firefox on the same machine - Works on Safari 26.3 and earlier
Attachments
ICE gathering issue reproducing sample
(10.11 KB, text/html)
2026-04-14 17:24 PDT
,
devalevd
devalevd: review?
Details
View All
Add attachment
proposed patch, testcase, etc.
Radar WebKit Bug Importer
Comment 1
2026-04-14 18:54:52 PDT
<
rdar://problem/174794660
>
devalevd
Comment 2
2026-04-15 12:11:31 PDT
You can also use the HTML sample reproduction I have attached or follow below browse console steps. # Reproduction steps in browser console ## Prerequisites - Obtain fresh TURN credentials (TTL is 300s, so run steps promptly) - Open Safari DevTools Console (⌘⌥C) ## Step 1 — Create PeerConnection with relay policy ```javascript const pc = new RTCPeerConnection({ iceServers: [{ urls: [ "turn:ice.m1.uw2.app.chime.aws:3478?transport=udp", "turns:ice.m1.uw2.app.chime.aws:443?transport=tcp" ], username: "1776279934:61b1cc80-38fd-11f1-b2b7-02260fc92e3f", credential: "UrxjkNiC6UCE7Lb63k5JqvjfXTk=" }], iceTransportPolicy: "relay" }); ``` ## Step 2 — Attach event listeners ```javascript const start = performance.now(); const elapsed = () => ((performance.now() - start) / 1000).toFixed(2) + "s"; pc.onicecandidate = e => { if (e.candidate) { console.log(`[${elapsed()}] CANDIDATE: ${e.candidate.type} ${e.candidate.protocol} ${e.candidate.address}:${e.candidate.port}`); } else { console.log(`[${elapsed()}] Gathering complete (null candidate)`); } }; pc.onicecandidateerror = e => { console.warn(`[${elapsed()}] ICE error: code=${e.errorCode} "${e.errorText}" url=${e.url}`); }; pc.onicegatheringstatechange = () => { console.log(`[${elapsed()}] iceGatheringState → ${pc.iceGatheringState}`); }; // Timeout watchdog setTimeout(() => { console.error(`[${elapsed()}] ⏰ FULL TIMEOUT REACHED — iceGatheringState: ${pc.iceGatheringState}`); pc.close(); }, 30000); ``` ## Step 3 — Add transceiver, create offer, start gathering ```javascript pc.addTransceiver("audio", { direction: "sendrecv" }); pc.createOffer().then(offer => { console.log(`[${elapsed()}] Offer created (ufrag=${offer.sdp.match(/a=ice-ufrag:(\S+)/)?.[1]})`); return pc.setLocalDescription(offer); }).then(() => { console.log(`[${elapsed()}] 🚀 setLocalDescription done — iceGatheringState: ${pc.iceGatheringState}`); }); ``` ## Expected Output ### MacOS 26 Safari 26.4 / Chrome / Firefox (working) ``` [0.01s] Offer created (ufrag=...) [0.01s] setLocalDescription done — iceGatheringState: gathering [0.02s] iceGatheringState → gathering [0.50s] CANDIDATE: relay udp <ip>:port [0.80s] CANDIDATE: relay tcp <ip>:port [0.81s] Gathering complete (null candidate) [0.81s] iceGatheringState → complete ``` ### MacOS 15 ( Safari 26.4 (broken) ``` ... [30.00s] ⏰ TIMEOUT — iceGatheringState: gathering ``` So ICE gathering never moves to complete state. Issue is with Safari 26.4 and so far consistently reproducible with Chime SDK TURN servers with relay with below macOS version: MacOS 15.7.5, 15.7.3 and 15.7.1
youenn fablet
Comment 3
2026-04-16 04:55:43 PDT
Pull request:
https://github.com/WebKit/WebKit/pull/62891
EWS
Comment 4
2026-04-17 00:11:16 PDT
Committed
311432@main
(da04f8d6cf5c): <
https://commits.webkit.org/311432@main
> Reviewed commits have been landed. Closing PR #62891 and removing active labels.
devalevd
Comment 5
2026-04-17 14:50:52 PDT
Thanks for the quick turnaround on the fix. We have customer reported high severity tickets and our metrics show that pretty much users using Mac OS 15.7.X (or 15.X) + Safari 26.4+ cannot join a WebRTC meeting through their application built on-top-of our JS SDK. The only workaround is to suggest them to use either a different browser, different Safari version < 26.4 if on MacOS 15 or upgrade the MacOS to v26 itself. Could you help us prioritizing this fix? Awaiting which version the fix would be in to test and accordingly to update our customers.
EWS
Comment 6
2026-04-20 22:23:34 PDT
Committed
305413.698@safari-7624-branch
(cbfb4c491ddf): <
https://commits.webkit.org/305413.698@safari-7624-branch
> Reviewed commits have been landed. Closing PR #5007 and removing active labels.
devalevd
Comment 7
2026-04-21 09:55:53 PDT
Hi Team, Thanks for the fix. Any update on which Safari version we could test this out on?
Note
You need to
log in
before you can comment on or make changes to this bug.
Top of Page
Format For Printing
XML
Clone This Bug