ngraziano / SharpRTSP

A RTSP handling library
Other
557 stars 182 forks source link

exception with overlapping writes in await stream.rtpChannel.WriteToControlPortAsync(rtcpSenderReport); #128

Closed RogerHardiman closed 2 months ago

RogerHardiman commented 2 months ago

Hi I was trying to add RTSPS support to my RtspCameraExample that I wrote many years ago. This causes an Exception to be raised where two Tasks in RtspServer.cs are writing to the socket Stream at the same time. The exception is System.NotSupportedException: This method may not be called when another write operation is pending.

As my RtspCameraExample and RtspServer.cs have been re-written with Tasks recently then I'm posting here for assistance with a fix.

To re-create... a) RTSPS is just RTSP TCP mode (RTP packets interleaved into the RTSP socket) on a TSL socket.

b) In RTSPTcpTransport is GetStream() that gets the Socket Stream. this is changed to use .Net's SslStream wrapper (which includes TLS) and I have to call AuthenticateAsServer() on the SslStream.

c) ffplay can then connect with a RTSPS and gets a little bit of video and a little bit of audio over the TLS connection but then SharpRTSP CameraExample will throw an exception as there are overlapping Writes() to the SslStream.

_System.NotSupportedException:  This method may not be called when another write operation is pending._
Sending video session 1 TCP Timestamp(ms)=7015. RTP timestamp=631350. Sequence=63211
Sending audio session 1 TCP Timestamp(ms)=7022. RTP timestamp=56176. Sequence=3300
Sending audio session 1 TCP Timestamp(ms)=7043. RTP timestamp=56344. Sequence=3301
Sending audio session 1 TCP Timestamp(ms)=7063. RTP timestamp=56504. Sequence=3302
Sending audio session 1 TCP Timestamp(ms)=7082. RTP timestamp=56656. Sequence=3303
fail: RtspCameraExample.RtspServer[0]
      Error writing RTCP to listener 127.0.0.1:14161
      System.NotSupportedException:  This method may not be called when another write operation is pending.
         at System.Net.Security.SslStream.WriteAsyncInternal[TIOAdapter](ReadOnlyMemory`1 buffer, CancellationToken cancellationToken)
         at Rtsp.RtspListener.SendDataAsync(Int32 channel, ReadOnlyMemory`1 frame) in C:\Users\roger\source\SharpRTSP_Local_Fork\RTSP\RTSPListener.cs:line 571
         at RtspCameraExample.RtspServer.SendRTCP(UInt32 rtp_timestamp, RTSPConnection connection, RTPStream stream) in C:\Users\roger\source\SharpRTSP_Local_Fork\RtspCameraExample\RtspServer.cs:line 626
warn: Rtsp.RtspListener[0]
      IO Error
      System.IO.IOException: Unable to read data from the transport connection: An established connection was aborted by the software in your host machine..
       ---> System.Net.Sockets.SocketException (10053): An established connection was aborted by the software in your host machine.
         at System.Net.Sockets.NetworkStream.Read(Span`1 buffer)
         --- End of inner exception stack trace ---
         at System.Net.Sockets.NetworkStream.Read(Span`1 buffer)
         at System.Net.Security.SyncReadWriteAdapter.ReadAsync(Stream stream, Memory`1 buffer, CancellationToken cancellationToken)
         at System.Net.Security.SslStream.EnsureFullTlsFrameAsync[TIOAdapter](CancellationToken cancellationToken, Int32 estimatedSize)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at System.Net.Security.SslStream.ReadAsyncInternal[TIOAdapter](Memory`1 buffer, CancellationToken cancellationToken)
         at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
         at System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count)
         at System.Net.Security.SslStream.ReadByte()
         at Rtsp.RtspListener.ReadOneMessageAsync(Stream commandStream, CancellationToken token) in C:\Users\roger\source\SharpRTSP_Local_Fork\RTSP\RTSPListener.cs:line 421
         at Rtsp.RtspListener.DoJobAsync(CancellationToken token) in C:\Users\roger\source\SharpRTSP_Local_Fork\RTSP\RTSPListener.cs:line 132
dbug: Rtsp.RtspListener[0]
      Connection Close

@ngraziano - I've not checked the commit logs to see if you did all the changes to Tasks, but am reaching out for some help as the code has changed a bit since I last worked on it.

Thanks Roger

RogerHardiman commented 2 months ago

Oh, forgot to add that to make the SslStream, I make a small change to RTSPTcpRlsTransport.cs so GetStream() knows if it is Client or Server

        public override Stream GetStream()
        {
            var isServer = true;  // TODO - This is a hard coded test

            var sslStream = new SslStream(base.GetStream(), leaveInnerStreamOpen: true, _userCertificateValidationCallback);

            if (isServer)
            {
                // Creaate your Self Sign certificate as follows
                // 1) Make c:\tls_certificate and CD into that folder

                // 2) Run   "\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\MakeCert.exe" -r -pe -n "CN=192.168.26.94" -sky exchange -sv server.pvk server.cer
                //    and when it asks for the PASSWORD in the Pop-up windows, select NONE
                //    (would be better to have a password, but passing the passowrd into new X509Certificate2 did not work for me. Probably user error

                // 3) Run   "\Program Files (x86)\Windows Kits\10\bin\10.0.22621.0\x64\pvk2pfx.exe" -pvk server.pvk -spc server.cer -pfx server.pfx

                var certificate = new X509Certificate2("c:\\tls_certificate\\server.pfx");
                sslStream.AuthenticateAsServer(certificate, false, SslProtocols.Tls12, false);
            }
            else
            {
                sslStream.AuthenticateAsClient(RemoteAddress);
            }
            return sslStream;
        }
RogerHardiman commented 2 months ago

Fixed by wrapping the Write() and WriteAsync() from SendData() and SendDataAsync() with this style of code

 await mySemaphoreSlim.WaitAsync();
 try {
     await Stuff();
 } finally {
     mySemaphoreSlim.Release();
}
ngraziano commented 2 months ago

Oh, ok, you found a problem with the asyncs method. I was too confident :D It seems there is a problem when the program send rtp et rtcp in async, rtp packet may not bee finish to be sent when the server send rtcp

At first, I thought that RtpTcpTransport need to have a lock to access to rtspListener but maybe it is not enough, program may send keep alive at same time (GET_PARAMETER/OPTIONS)

You are right a locking is needed in SendData and SendDataAsync. I may have remove a lock by error (I have a vague memory of doing it)

Tell me if you will make the change, or if I will make it during this week.

RogerHardiman commented 2 months ago

I will do the change. I will just commit and PR the locking fix for now and then commit the RTSPS server as a 2nd change.

RogerHardiman commented 2 months ago

PR #129 has my changes for this bug report, and also adds RTSPS Server Support in RTSPCameraExamp

RogerHardiman commented 2 months ago

Thanks for merging the PR.