6969using System . Collections . Generic ;
7070using System . Linq ;
7171using System . Net ;
72+ using System . Net . Security ;
7273using System . Net . Sockets ;
7374using System . Runtime . CompilerServices ;
7475using System . Text ;
@@ -439,6 +440,15 @@ public override void Close(string reason)
439440
440441 internal IceServerResolver _iceServerResolver = new IceServerResolver ( ) ;
441442
443+ // Tracks one SslStream per TURNS/STUNS server URI so subsequent sends reuse the TLS session.
444+ private ConcurrentDictionary < STUNUri , SslStream > _tlsStreams = new ConcurrentDictionary < STUNUri , SslStream > ( ) ;
445+
446+ // Per-URI write gate. SslStream.Write is NOT thread-safe and throws
447+ // "another write operation is pending" if two threads (e.g. ICE
448+ // connectivity checks + TURN permission refreshes + framed SCTP data)
449+ // race. We also use this to make the lazy handshake atomic.
450+ private ConcurrentDictionary < STUNUri , SemaphoreSlim > _tlsWriteLocks = new ConcurrentDictionary < STUNUri , SemaphoreSlim > ( ) ;
451+
442452 private IceServer _activeIceServer ;
443453
444454 public RTCIceComponent Component { get ; private set ; }
@@ -769,6 +779,13 @@ protected void StartTcpRtpReceiver()
769779 var stunUri = pair . Key ;
770780 var tcpSocket = pair . Value ;
771781
782+ // TURNS/STUNS use an SslStream owned by SendOverTCP/StartTlsReadLoop, so the
783+ // plain TCP receiver loop must not also try to read from the raw socket.
784+ if ( stunUri != null && ( stunUri . Scheme == STUNSchemesEnum . turns || stunUri . Scheme == STUNSchemesEnum . stuns ) )
785+ {
786+ continue ;
787+ }
788+
772789 if ( stunUri != null && ! m_rtpTcpReceiverByUri . ContainsKey ( stunUri ) && tcpSocket != null )
773790 {
774791 var rtpTcpReceiver = new IceTcpReceiver ( tcpSocket ) ;
@@ -1065,7 +1082,7 @@ private void RefreshTurn(Object state)
10651082 {
10661083 return ;
10671084 }
1068- if ( _activeIceServer . _uri . Scheme != STUNSchemesEnum . turn || NominatedEntry . LocalCandidate . IceServer is null )
1085+ if ( ( _activeIceServer . _uri . Scheme != STUNSchemesEnum . turn && _activeIceServer . _uri . Scheme != STUNSchemesEnum . turns ) || NominatedEntry . LocalCandidate . IceServer is null )
10691086 {
10701087 _refreshTurnTimer ? . Dispose ( ) ;
10711088 return ;
@@ -1155,8 +1172,8 @@ private void CheckIceServers(Object state)
11551172 logger . LogDebug ( "RTP ICE Channel was not able to acquire an active ICE server, stopping ICE servers timer." ) ;
11561173 _processIceServersTimer . Dispose ( ) ;
11571174 }
1158- else if ( ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . turn && _activeIceServer . RelayEndPoint != null ) ||
1159- ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . stun && _activeIceServer . ServerReflexiveEndPoint != null ) )
1175+ else if ( ( ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . turn || _activeIceServer . _uri . Scheme == STUNSchemesEnum . turns ) && _activeIceServer . RelayEndPoint != null ) ||
1176+ ( ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . stun || _activeIceServer . _uri . Scheme == STUNSchemesEnum . stuns ) && _activeIceServer . ServerReflexiveEndPoint != null ) )
11601177 {
11611178 // Successfully set up the ICE server. Do nothing.
11621179 }
@@ -1185,13 +1202,13 @@ private void CheckIceServers(Object state)
11851202 _activeIceServer . Error = SocketError . TimedOut ;
11861203 }
11871204 // Send STUN binding request.
1188- else if ( _activeIceServer . ServerReflexiveEndPoint == null && _activeIceServer . _uri . Scheme == STUNSchemesEnum . stun )
1205+ else if ( _activeIceServer . ServerReflexiveEndPoint == null && ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . stun || _activeIceServer . _uri . Scheme == STUNSchemesEnum . stuns ) )
11891206 {
11901207 logger . LogDebug ( "Sending STUN binding request to ICE server {Uri} with address {EndPoint}." , _activeIceServer . _uri , _activeIceServer . ServerEndPoint ) ;
11911208 _activeIceServer . Error = SendStunBindingRequest ( _activeIceServer ) ;
11921209 }
11931210 // Send TURN binding request.
1194- else if ( _activeIceServer . ServerReflexiveEndPoint == null && _activeIceServer . _uri . Scheme == STUNSchemesEnum . turn )
1211+ else if ( _activeIceServer . ServerReflexiveEndPoint == null && ( _activeIceServer . _uri . Scheme == STUNSchemesEnum . turn || _activeIceServer . _uri . Scheme == STUNSchemesEnum . turns ) )
11951212 {
11961213 logger . LogDebug ( "Sending TURN allocate request to ICE server {Uri} with address {EndPoint}." , _activeIceServer . _uri , _activeIceServer . ServerEndPoint ) ;
11971214 _activeIceServer . Error = SendTurnAllocateRequest ( _activeIceServer ) ;
@@ -2411,27 +2428,79 @@ protected virtual SocketError SendOverTCP(IceServer iceServer, byte[] buffer)
24112428 return e1 . Port == e2 . Port && e1 . Address . Equals ( e2 . Address ) ;
24122429 } ;
24132430
2414- if ( ! sendSocket . Connected || ! ( sendSocket . RemoteEndPoint is IPEndPoint ) || ! equals ( sendSocket . RemoteEndPoint as IPEndPoint , dstEndPoint ) )
2431+ bool isTls = iceServer . _uri . Scheme == STUNSchemesEnum . turns || iceServer . _uri . Scheme == STUNSchemesEnum . stuns ;
2432+ if ( isTls )
24152433 {
2416- if ( sendSocket . Connected )
2434+ // --- TLS PATH (TURNS / STUNS) ---
2435+ // SslStream.Write is not thread-safe, and the lazy handshake/connect
2436+ // must also be atomic so two threads don't both create a stream.
2437+ var writeLock = _tlsWriteLocks . GetOrAdd ( iceServer . _uri , _ => new SemaphoreSlim ( 1 , 1 ) ) ;
2438+ writeLock . Wait ( ) ;
2439+ try
24172440 {
2418- logger . LogDebug ( "SendOverTCP request disconnect." ) ;
2419- sendSocket . Disconnect ( true ) ;
2420- }
2421- sendSocket . Connect ( dstEndPoint ) ;
2441+ if ( ! _tlsStreams . TryGetValue ( iceServer . _uri , out SslStream sslStream ) )
2442+ {
2443+ // Connect the raw socket if needed
2444+ if ( ! sendSocket . Connected )
2445+ {
2446+ sendSocket . Connect ( dstEndPoint ) ;
2447+ }
24222448
2423- logger . LogDebug ( "SendOverTCP status: {Status} endpoint: {EndPoint}" , sendSocket . Connected , dstEndPoint ) ;
2424- }
2449+ // Wrap in SslStream. Validation is left permissive here; tighten if needed.
2450+ sslStream = new SslStream ( new NetworkStream ( sendSocket , false ) , false ,
2451+ ( sender , cert , chain , errors ) => true , null ) ;
24252452
2426- //Fix ReceiveFrom logic if any previous exception happens
2427- m_rtpTcpReceiverByUri . TryGetValue ( iceServer ? . _uri , out IceTcpReceiver rtpTcpReceiver ) ;
2428- if ( rtpTcpReceiver != null && ! rtpTcpReceiver . IsRunningReceive && ! rtpTcpReceiver . IsClosed )
2429- {
2430- rtpTcpReceiver . BeginReceiveFrom ( ) ;
2453+ try
2454+ {
2455+ // Perform TLS handshake using the hostname from the URI for SNI/cert match.
2456+ sslStream . AuthenticateAsClient ( iceServer . _uri . Host ) ;
2457+
2458+ _tlsStreams . TryAdd ( iceServer . _uri , sslStream ) ;
2459+ logger . LogDebug ( "TLS handshake successful for {Uri}" , iceServer . _uri ) ;
2460+
2461+ // Start a dedicated read loop for this SSL stream
2462+ _ = StartTlsReadLoop ( iceServer . _uri , sslStream , dstEndPoint ) ;
2463+ }
2464+ catch ( Exception tlsEx )
2465+ {
2466+ logger . LogError ( tlsEx , "TLS handshake failed for {Uri}: {Message}" , iceServer . _uri , tlsEx . Message ) ;
2467+ return SocketError . SocketError ;
2468+ }
2469+ }
2470+
2471+ // Write to the SSL stream (serialised by writeLock).
2472+ sslStream . Write ( buffer ) ;
2473+ return SocketError . Success ;
2474+ }
2475+ finally
2476+ {
2477+ writeLock . Release ( ) ;
2478+ }
24312479 }
2480+ else
2481+ {
2482+ if ( ! sendSocket . Connected || ! ( sendSocket . RemoteEndPoint is IPEndPoint ) || ! equals ( sendSocket . RemoteEndPoint as IPEndPoint , dstEndPoint ) )
2483+ {
2484+ if ( sendSocket . Connected )
2485+ {
2486+ logger . LogDebug ( "SendOverTCP request disconnect." ) ;
2487+ sendSocket . Disconnect ( true ) ;
2488+ }
2489+ sendSocket . Connect ( dstEndPoint ) ;
24322490
2433- sendSocket . BeginSendTo ( buffer , 0 , buffer . Length , SocketFlags . None , dstEndPoint , EndSendToTCP , sendSocket ) ;
2434- return SocketError . Success ;
2491+ logger . LogDebug ( "SendOverTCP status: {Status} endpoint: {EndPoint}" , sendSocket . Connected , dstEndPoint ) ;
2492+ }
2493+
2494+ //Fix ReceiveFrom logic if any previous exception happens
2495+ m_rtpTcpReceiverByUri . TryGetValue ( iceServer ? . _uri , out IceTcpReceiver rtpTcpReceiver ) ;
2496+ if ( rtpTcpReceiver != null && ! rtpTcpReceiver . IsRunningReceive && ! rtpTcpReceiver . IsClosed )
2497+ {
2498+ rtpTcpReceiver . BeginReceiveFrom ( ) ;
2499+ }
2500+
2501+ sendSocket . BeginSendTo ( buffer , 0 , buffer . Length , SocketFlags . None , dstEndPoint , EndSendToTCP , sendSocket ) ;
2502+ return SocketError . Success ;
2503+ }
24352504 }
24362505 catch ( ObjectDisposedException ) // Thrown when socket is closed. Can be safely ignored.
24372506 {
@@ -2449,6 +2518,77 @@ protected virtual SocketError SendOverTCP(IceServer iceServer, byte[] buffer)
24492518 }
24502519 }
24512520
2521+ /// <summary>
2522+ /// Reads framed STUN / TURN traffic off a TLS stream (TURNS / STUNS). STUN and TURN
2523+ /// allocation messages share a 20-byte header where bytes 2-3 are the body length in
2524+ /// big-endian; TURN channel-data messages instead use a 4-byte header in the
2525+ /// 0x4000-0x7FFF channel-number range. We accumulate bytes until we have a full
2526+ /// message and then dispatch it via OnRTPPacketReceived (inherited from RTPChannel).
2527+ /// </summary>
2528+ private async Task StartTlsReadLoop ( STUNUri uri , SslStream sslStream , IPEndPoint remoteEndPoint )
2529+ {
2530+ byte [ ] receiveBuffer = new byte [ 4096 ] ;
2531+ List < byte > streamBuffer = new List < byte > ( ) ;
2532+
2533+ logger . LogDebug ( "Starting TLS read loop for {Uri}" , uri ) ;
2534+
2535+ try
2536+ {
2537+ while ( ! IsClosed && sslStream . CanRead )
2538+ {
2539+ int bytesRead = await sslStream . ReadAsync ( receiveBuffer , 0 , receiveBuffer . Length ) . ConfigureAwait ( false ) ;
2540+
2541+ if ( bytesRead == 0 )
2542+ {
2543+ logger . LogWarning ( "TLS stream closed remotely for {Uri}" , uri ) ;
2544+ break ;
2545+ }
2546+
2547+ streamBuffer . AddRange ( new ArraySegment < byte > ( receiveBuffer , 0 , bytesRead ) ) ;
2548+
2549+ // Process complete packets from the stream buffer
2550+ while ( streamBuffer . Count >= 4 ) // Minimum header size
2551+ {
2552+ int bodyLength = ( streamBuffer [ 2 ] << 8 ) | streamBuffer [ 3 ] ;
2553+ int totalPacketLength = 20 + bodyLength ; // STUN/TURN header (20) + body
2554+
2555+ // TURN Channel Data has a 4-byte header; channel range is 0x4000 -> 0x7FFF
2556+ if ( streamBuffer [ 0 ] >= 0x40 && streamBuffer [ 0 ] <= 0x7F )
2557+ {
2558+ totalPacketLength = 4 + bodyLength ;
2559+ }
2560+
2561+ if ( streamBuffer . Count >= totalPacketLength )
2562+ {
2563+ byte [ ] packetBytes = streamBuffer . GetRange ( 0 , totalPacketLength ) . ToArray ( ) ;
2564+ streamBuffer . RemoveRange ( 0 , totalPacketLength ) ;
2565+ OnRTPPacketReceived ( null , 0 , remoteEndPoint , packetBytes ) ;
2566+ }
2567+ else
2568+ {
2569+ // Not enough data yet, wait for the next ReadAsync.
2570+ break ;
2571+ }
2572+ }
2573+ }
2574+ }
2575+ catch ( Exception ex )
2576+ {
2577+ if ( ! IsClosed )
2578+ {
2579+ logger . LogError ( ex , "TLS read loop exception for {Uri}: {Message}" , uri , ex . Message ) ;
2580+ }
2581+ }
2582+ finally
2583+ {
2584+ _tlsStreams . TryRemove ( uri , out _ ) ;
2585+ if ( _tlsWriteLocks . TryRemove ( uri , out var removedLock ) )
2586+ {
2587+ removedLock . Dispose ( ) ;
2588+ }
2589+ }
2590+ }
2591+
24522592 protected virtual void EndSendToTCP ( IAsyncResult ar )
24532593 {
24542594 try
0 commit comments