SpawnDev.WebTorrent's desktop client uses SipSorcery 10.0.3 (NuGet) for WebRTC. It can connect to other SipSorcery peers (Desktop_TwoClients test passes in 7s) but CANNOT connect to browser-based JS WebTorrent peers (all 7 Sintel network tests fail).
The browser peers are running standard JS WebTorrent with @thaunknown/simple-peer (WebRTC abstraction layer) in Chrome/Firefox/Edge.
SpawnDev.RTLink successfully connects SipSorcery peers to browser peers. Key difference: RTLink uses a bundled, modified copy of SipSorcery source code, NOT the NuGet package.
Location: D:/users/tj/Projects/SpawnDev.RTLink/SpawnDev.RTLink/SIPSorcery/
RTLink's working configuration:
- RTCConfiguration with only ICE servers (no special flags)
- No SDP munging/transforms at application layer
- SDP is passed as raw strings between peers
- Data channel named "RPC" (ordered, not negotiated)
- Trickle ICE with individual candidate exchange via relay
- DTLS fingerprint upper-cased: Firefox requires upper-case hex in fingerprint
- Chrome ICE role fix: Forces
IceRole = activewhen receiving offers - Modern SCTP format:
m=application 9 UDP/DTLS/SCTP webrtc-datachannel - Accepts both legacy and modern:
DTLS/SCTPandUDP/DTLS/SCTP - Data channel via DCEP: In-band negotiation, not SDP-negotiated
// SipSorceryPeer.cs - our current setup
var config = new RTCConfiguration();
config.iceServers = new List<RTCIceServer>();
foreach (var url in _iceServers)
config.iceServers.Add(new RTCIceServer { urls = url });
_pc = new RTCPeerConnection(config);- Create RTCPeerConnection
- If initiator: createDataChannel, createOffer, setLocalDescription, waitForICE, emit SDP
- If responder: ondatachannel handler, setRemoteDescription(offer), createAnswer, setLocalDescription, waitForICE, emit SDP
- SDP is sent through WebSocket tracker as offer/answer
We apply FilterTrickle() which removes a=ice-options:trickle\s\n.
Potential issue: SipSorcery generates a=ice-options:ice2,trickle which does NOT match the regex a=ice-options:trickle\s\n. So the ice-options line remains. JS simple-peer also only removes exactly a=ice-options:trickle\s\n. So both sides may have slightly different ice-options content.
SipSorcery NuGet 10.0.3 may generate SDP that browsers reject or misparse.
Known SDP differences between SipSorcery and browsers:
- Origin line: SipSorcery uses
o=- <session_id> 0 IN IP4 <local_ip>, browsers vary - ICE options: SipSorcery adds
ice2,trickle, browsers typically have justtrickleor nothing - Fingerprint position: Session-level vs media-level placement
- Candidate format: Different priority calculations
- SCTP format:
UDP/DTLS/SCTPvs legacyDTLS/SCTP - Setup role:
actpass(offer) vsactive(answer) - CRITICAL
RTLink's bundled SipSorcery may have critical browser compatibility patches that the official NuGet package lacks. This is the most actionable investigation.
SipSorcery's ICE gathering may not include all candidates before we read the SDP. Our 2s candidate silence fallback + 15s timeout should handle this, but edge cases exist.
JS simple-peer creates data channels with random hex labels (20 bytes). SipSorcery may have issues with certain label characters or lengths.
The DTLS handshake between SipSorcery and browser WebRTC may fail silently. SipSorcery uses a self-signed cert with specific parameters that may not be compatible with all browser DTLS implementations.
The RTLink vs NuGet 10.0.3 comparison revealed that the entire DTLS/SRTP stack was rewritten between the version RTLink bundles and the 10.0.3 NuGet package.
RTLink (works with browsers) - Old BouncyCastle DTLS:
- Uses
Org.BouncyCastle.Crypto.Tls(old API) - DtlsSrtpClient.cs: 453 lines, extends
DefaultTlsClient - DtlsSrtpServer.cs: 584 lines, extends
DefaultTlsServer - DtlsSrtpTransport.cs: 616 lines, full handshake management
- Only offers
SRTP_AES128_CM_HMAC_SHA1_80(universally supported)
NuGet 10.0.3 (fails with browsers) - New "SharpSRTP" by Lukas Volf (Dec 2025):
- Uses
Org.BouncyCastle.Tls(new API) - DtlsSrtpClient.cs: 162 lines, extends new
DtlsClient - DtlsSrtpServer.cs: 131 lines, extends new
DtlsServer - DtlsSrtpTransport.cs: 177 lines, simplified with
SrtpSessionContext - Offers 12 SRTP profiles (AES-256-GCM, AES-128-GCM, ARIA, etc.)
- SRTP profile negotiation: Offering 12 profiles vs 1 could cause negotiation failures if the browser doesn't support the first offered profile, or if the profile ordering doesn't match browser preferences
- DTLS handshake implementation: Entirely different code paths for the handshake. Any bug in the new SharpSRTP library's DTLS implementation with Chrome/Firefox would break connectivity
- Key derivation: Different SRTP key extraction mechanism (old uses
IPacketTransformer/SrtpPolicy, new usesSrtpSessionContext/SrtpKeys) - Server has
ForceDisableMKI = true: RFC 8827 compliance change that may not be compatible with all browser implementations
| Area | RTLink (works) | NuGet 10.0.3 (fails) |
|---|---|---|
createAnswer() IceRole |
Doesn't explicitly enforce | Enforces RFC 4145 Section 4.1 |
| DTLS client construction | 2 params (cert, key) | 4 params (crypto, cert, key, sigAlg) |
| ICE gathering | Simple task + wait | Configurable timeout with CTS |
| Data channel cleanup | Not explicit | Explicit iteration + close |
| DTLS fingerprint computation | Local BouncyCastle digest | Delegates to SharpSRTP library |
- SDP format: both produce
m=application 9 UDP/DTLS/SCTP webrtc-datachannel - DTLS fingerprint toString: both use upper-case hex
- ICE role handling in setRemoteDescription: identical logic
- ICE candidate format: identical
- SDPMediaAnnouncement serialization: identical
- Default IceRole: both
actpass - ICE options: both
ice2,trickle
Copy RTLink's bundled SipSorcery source into SpawnDev.WebTorrent and build against it instead of the NuGet package. This is proven to work with browsers.
Pros: Known working, fast to implement Cons: Maintaining a forked copy, missing newer SipSorcery features
Find the last SipSorcery NuGet version that uses the old DTLS stack (before SharpSRTP rewrite). Pin to that version.
Pros: Uses official package, no source modification Cons: Need to identify the correct version, may miss other fixes
Add detailed DTLS handshake logging, identify exactly where the new stack fails with browsers, and fix it.
Pros: Fixes the root cause, benefits all SipSorcery users Cons: Most time-consuming, requires deep DTLS knowledge
Configure the new SipSorcery to only offer SRTP_AES128_CM_HMAC_SHA1_80 (the profile RTLink's version offers). This may be the simplest fix if the issue is profile negotiation.
Pros: Minimal change, tests a specific hypothesis Cons: May not be the actual issue if the DTLS handshake itself is broken
- Try Option D first - restrict SRTP profiles (lowest effort, tests hypothesis)
- Add DTLS handshake logging - log every state transition to find where it fails
- If DTLS is broken: try Option B - find the last working NuGet version
- If no older version works: try Option A - bundle RTLink's copy
- Long term: fix upstream - report issue to SipSorcery repo with reproduction
SpawnDev.WebTorrent/SipSorceryPeer.cs- Our WebRTC peerSpawnDev.WebTorrent/SimplePeer.cs- Base classSpawnDev.WebTorrent/WebSocketTracker.cs- Tracker signaling
SpawnDev.RTLink/RTLinkSIPSorc/RPCWebRTCSIPConnection.cs- Working SipSorcery peerSpawnDev.RTLink/SIPSorcery/net/WebRTC/RTCPeerConnection.cs- Modified SipSorcery sourceSpawnDev.RTLink/SIPSorcery/net/SDP/SDPMediaAnnouncement.cs- SDP generation
webtorrent-reference-client/node_modules/@thaunknown/simple-peer/lite.js- JS WebRTC
| Test | Status | Notes |
|---|---|---|
| Desktop_TwoClients | PASS (7s) | SipSorcery-to-SipSorcery works |
| CSharp_Downloads_From_JsWebTorrent | PASS (13s) | JS-to-C# via our tracker |
| Network_TrackerConnect_Announces | FAIL | SipSorcery can't reach Sintel browser peers |
| Network_MagnetAdd_PeersFound | FAIL | Same issue |
| Network_MagnetAdd_MetadataReceived | FAIL | Same issue |
| Network_LiveSwarm_DownloadsPieces | FAIL | Same issue |
| + 3 more Sintel tests | FAIL | All same root cause |