Sitelet https://github.com/npgsql/npgsql/issues/6274
Skip to content

.NET Framework broken in 8.0.8 for first connect #6274

Description

@trivalik

My project is .NET Framework 4.8 and end up in Exception while reading from stream with inner exception Von der Übertragungsverbindung können keine Daten gelesen werden: Eine vorhandene Verbindung wurde vom Remotehost geschlossen.

My test code

            var connectionString = "server=localhost;port=5432;database=postgres;user id=postgres;password='postgres';";
            using (var dataSource = NpgsqlDataSource.Create(connectionString))
            {
                using (var command = dataSource.CreateCommand("SELECT current_database() as db"))
                {
                    using (var reader = command.ExecuteReader())
                        while (reader.Read())
                        {
                            Console.WriteLine($"Database: {reader["db"]}");
                        }
                }
            }

pg_hba.conf of windows postgres 15.7 server:

hostssl all             all             all                     cert clientname=CN
local   all             all                                     scram-sha-256
host    all             all             all                     scram-sha-256  # only required if not localhost

postgresql.conf requires ssl on:

ssl = on
ssl_cert_file = 'server.crt'
ssl_key_file = 'server.key'
ssl_ca_file = 'client_ca.crt'

it is als reproduceable without: ssl_cert_file, ssl_key_file, ssl_ca_file, then it will end up with a proper error message client certificates can only be checked if a root certificate store is available for .NET 8. .NET Framework 4.8 fails again.

Fun Fact:

  • If I build npgsql 8.0.8 in debug it works, if I build release it fails with above error.
  • it happens only on the very first connect
  • works with .NET 8

Activity

  1. NinoFloris commented on Oct 29, 2025

    @NinoFloris
    Member

    This really sounds like configuration error, you may have a config file containing your connection details which might be different per build configuration? Are you stating the code sample you provided is enough to reproduce the problem? If so we should have already had a lot more reports.

  2. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    I don't think so, since if I change the target of the project to .NET 8, all works fine. I added "it happens only on the very first connect" and "works with .NET 8" to first post.

    edit: I see I missed the SSL configuration part.
    Additionally I tested my IP address, instead of localhost, same result, to be sure that the windows localhost windows update is not the cause.

    edit: I tested it on 4 machines, Win10 22H2, Win11 23H2/24H2/25H2

  3. changed the title [-].NET Framework broken in 8.0.8[/-] [+].NET Framework broken in 8.0.8 for first connect[/+] on Oct 30, 2025
  4. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    I tried to find the commit since this is the case. It is #4005 / 7ac49cc. The actual root cause are the new assignments in NpgsqlConnectionStringBuilder.Init(), especially SslMode = SslMode.Prefer. So appending SSL Mode=Disable to connection string lead to work again .

    Means this behavior is since npgsql version 6.

  5. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    Another dig in finds that TLS1.3 seems not working well in .NET Framework, see #5823. By default disable TLS1.3 for .NET Framework would make sense.

    The switch NpgsqlConnector.DisableSystemDefaultTlsVersions can only be influenced with registry. Right? This is not what I require.

  6. vonzshik commented on Oct 30, 2025

    @vonzshik
    Contributor

    Another dig in finds that TLS1.3 seems not working well in .NET Framework

    That's certainly not true, according to official documentation TLS 1.3 is supported starting with .NET Framework 4.6.2. In addition, we enable it if and only if ServicePointManager.DisableSystemDefaultTlsVersions is set to false, and with this property not existing at least until .NET Framework 4.7 (I only checked the source code for 4.6.2) that means that TLS 1.3 isn't going to be enabled for unsupported versions of .NET Framework.

  7. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    I see, anyway setting AppContext.SetSwitch("Switch.System.Net.DontEnableSystemDefaultTlsVersions", true); does not fix my issue. I thought initially it could be related.

  8. vonzshik commented on Oct 30, 2025

    @vonzshik
    Contributor

    You should check postgres logs, but most likely what happens is that for some reason postgres doesn't allow to authenticate with ssl, in which case it returns an error and immediately breaks the connection. But due to a way how postgres terminates connection and how windows handles it, what happens is that windows receives the error on the socket and also rst packet, and the moment rst packet is read the whole internal buffer is cleared, leading to that error message being lost.

  9. vonzshik commented on Oct 30, 2025

    @vonzshik
    Contributor

    Maybe we should always retry for prefer/allow instead of only doing that whenever we get a specific error

    catch (PostgresException e)
    when (e.SqlState == PostgresErrorCodes.InvalidAuthorizationSpecification &&
    (sslMode == SslMode.Prefer && conn.IsSecure || sslMode == SslMode.Allow && !conn.IsSecure))
    {
    cancellationRegistration.Dispose();
    Debug.Assert(!conn.IsBroken);
    conn.Cleanup();
    // If Prefer was specified and we failed (with SSL), retry without SSL.
    // If Allow was specified and we failed (without SSL), retry with SSL
    await OpenCore(
    conn,
    sslMode == SslMode.Prefer ? SslMode.Disable : SslMode.Require,
    timeout,
    async,
    cancellationToken,
    isFirstAttempt: false).ConfigureAwait(false);
    return;
    }

  10. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    @vonzshik Change the catch to just Exception would fix it. BTW the error in this case is (same line number as b732322):

    Npgsql.NpgsqlException (0x80004005): Exception while reading from stream ---> System.IO.IOException: Von der Übertragungsverbindung können keine Daten gelesen werden: Eine vorhandene Verbindung wurde vom Remotehost geschlossen. ---> System.Net.Sockets.SocketException: Eine vorhandene Verbindung wurde vom Remotehost geschlossen
       bei System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
       --- Ende der internen Ausnahmestapelüberwachung ---
       bei System.Net.Sockets.NetworkStream.Read(Byte[] buffer, Int32 offset, Int32 size)
       bei System.Net.FixedSizeReader.ReadPacket(Byte[] buffer, Int32 offset, Int32 count)
       bei System.Net.Security._SslStream.StartFrameHeader(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
       bei System.Net.Security._SslStream.StartReading(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
       bei System.Net.Security._SslStream.ProcessRead(Byte[] buffer, Int32 offset, Int32 count, AsyncProtocolRequest asyncRequest)
       bei System.Net.Security.SslStream.Read(Byte[] buffer, Int32 offset, Int32 count)
       bei Npgsql.Internal.NpgsqlReadBuffer.<<Ensure>g__EnsureLong|55_0>d.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlReadBuffer.cs:Zeile 330.
       bei Npgsql.Internal.NpgsqlReadBuffer.<<Ensure>g__EnsureLong|55_0>d.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlReadBuffer.cs:Zeile 417.
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Runtime.CompilerServices.ConfiguredValueTaskAwaitable.ConfiguredValueTaskAwaiter.GetResult()
       bei Npgsql.Internal.NpgsqlConnector.<ReadMessageLong>d__231.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.cs:Zeile 1465.
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Threading.Tasks.ValueTask`1.get_Result()
       bei Npgsql.Internal.NpgsqlConnector.<Authenticate>d__0.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.Auth.cs:Zeile 24.
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
       bei Npgsql.Internal.NpgsqlConnector.<<Open>g__OpenCore|211_1>d.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.cs:Zeile 559.
    

    On Second connect the correct exception appears:

    Npgsql.PostgresException (0x80004005): 28000: connection requires a valid client certificate
       bei Npgsql.Internal.NpgsqlConnector.<ReadMessageLong>d__231.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.cs:Zeile 1458.
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Threading.Tasks.ValueTask`1.get_Result()
       bei Npgsql.Internal.NpgsqlConnector.<Authenticate>d__0.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.Auth.cs:Zeile 24.
    --- Ende der Stapelüberwachung vom vorhergehenden Ort, an dem die Ausnahme ausgelöst wurde ---
       bei System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
       bei System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
       bei System.Runtime.CompilerServices.ConfiguredTaskAwaitable.ConfiguredTaskAwaiter.GetResult()
       bei Npgsql.Internal.NpgsqlConnector.<<Open>g__OpenCore|211_1>d.MoveNext() in \npgsql\src\Npgsql\Internal\NpgsqlConnector.cs:Zeile 559.
      Exception data:
        Severity: FATAL
        SqlState: 28000
        MessageText: connection requires a valid client certificate
        File: auth.c
        Line: 422
        Routine: ClientAuthentication
    
  11. roji commented on Oct 30, 2025

    @roji
    Member

    Just to be clear - the problem here is that an incorrect exception is being thrown on the 1st attempt (and not on the 2nd), on .NET Framework? In other words, it's expected for the connection to fail, and the problem is just the kind of exception?

  12. vonzshik commented on Oct 30, 2025

    @vonzshik
    Contributor

    Whether it's .NET Framework or .NET doesn't really matter, it's just a race condition between us reading the correct error and also reading rst packet, which on windows clears the whole buffer and leads us to being unable to return a correct exception.

  13. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    In my opinion it is only .NET Framework related. I had in past to do other fixes that get .NET Framework working with SSL. The point of the race condition I have no idea if this is the case, but I do not believe it.

  14. vonzshik commented on Oct 30, 2025

    @vonzshik
    Contributor
  15. trivalik commented on Oct 30, 2025

    @trivalik
    ContributorAuthor

    I do not say that this could not happen. I do say that in this case probably an issue in .NET Framework implementation lead to this. During my currents tests, I had never issues in .NET 8.

  16. trivalik commented on Nov 3, 2025

    @trivalik
    ContributorAuthor

    Here Wiresharks, which shows that for what ever reason .NET 8 is just going on after RST, where .NET Framework 4.8 stops.

    Image

    pcapFiles.zip

  17. NinoFloris commented on Nov 3, 2025

    @NinoFloris
    Member

    I would be more interested in why we end up getting an RST in the first place, this would also help for .NET 8+.

  18. vonzshik commented on Nov 3, 2025

    @vonzshik
    Contributor

    I would be more interested in why we end up getting an RST in the first place, this would also help for .NET 8+.

    That's how Postgres works, it just drops the connection (and socket). It works fine on linux because there RST doesn't clear the buffer, but on Windows it does.

    I do not say that this could not happen. I do say that in this case probably an issue in .NET Framework implementation lead to this. During my currents tests, I had never issues in .NET 8.

    Well, just for fun I tried to write a small repro for .NET 9, and it does fail at startup. So again, this has nothing to do with .NET vs .NET Framework, that's a race condition.

    Npgsql.NpgsqlException (0x80004005): Exception while reading from stream
     ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host..
     ---> System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
       at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow)
       at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ReceiveAsync(Socket socket, CancellationToken cancellationToken)
       at System.Net.Sockets.Socket.ReceiveAsync(Memory`1 buffer, SocketFlags socketFlags, Boolean fromNetworkStream, CancellationToken cancellationToken)
       at System.Net.Sockets.NetworkStream.ReadAsync(Memory`1 buffer, CancellationToken cancellationToken)
       at System.Net.Security.SslStream.EnsureFullTlsFrameAsync[TIOAdapter](CancellationToken cancellationToken, Int32 estimatedSize)
       at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.ExecutionContextCallback(Object s)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.MoveNext()
       at System.Net.Sockets.SocketAsyncEventArgs.<>c.<.cctor>b__174_0(UInt32 errorCode, UInt32 numBytes, NativeOverlapped* nativeOverlapped)
       at System.Threading.PortableThreadPool.IOCompletionPoller.PollAndInlineCallbacks()
    --- End of stack trace from previous location ---
    
       --- End of inner exception stack trace ---
       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 Npgsql.Internal.NpgsqlReadBuffer.<Ensure>g__EnsureLong|55_0(NpgsqlReadBuffer buffer, Int32 count, Boolean async, Boolean readingNotifications)
       at Npgsql.Internal.NpgsqlReadBuffer.<Ensure>g__EnsureLong|55_0(NpgsqlReadBuffer buffer, Int32 count, Boolean async, Boolean readingNotifications)
       at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token)
       at Npgsql.Internal.NpgsqlConnector.ReadMessageLong(Boolean async, DataRowLoadingMode dataRowLoadingMode, Boolean readingNotifications, Boolean isReadingPrependedMessage)
       at System.Runtime.CompilerServices.PoolingAsyncValueTaskMethodBuilder`1.StateMachineBox`1.System.Threading.Tasks.Sources.IValueTaskSource<TResult>.GetResult(Int16 token)
       at Npgsql.Internal.NpgsqlConnector.Authenticate(String username, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
       at Npgsql.Internal.NpgsqlConnector.<Open>g__OpenCore|214_1(NpgsqlConnector conn, SslMode sslMode, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
       at Npgsql.Internal.NpgsqlConnector.Open(NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
       at Npgsql.UnpooledDataSource.Get(NpgsqlConnection conn, NpgsqlTimeout timeout, Boolean async, CancellationToken cancellationToken)
       at Npgsql.NpgsqlConnection.<Open>g__OpenAsync|42_0(Boolean async, CancellationToken cancellationToken)
       at Npgsql.NpgsqlDataSource.OpenConnectionAsync(CancellationToken cancellationToken)
       at Npgsql.NpgsqlDataSource.OpenConnectionAsync(CancellationToken cancellationToken)
    
    using Npgsql;
    
    const string connString = "Host=localhost; Database=database; User ID=postgres; Password=password; Pooling=false";
    
    var dataSource = NpgsqlDataSource.Create(connString);
    
    var tasks = new List<Task>();
    
    for (var i = 0; i < 50; i++)
    {
        tasks.Add(Task.Run(async () =>
        {
            while (true)
            {
                try
                {
                    await using var connection = await dataSource.OpenConnectionAsync();
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex);
                }   
            }
        }));
    }
    
    await Task.WhenAll(tasks);
  19. added theissue type on Nov 5, 2025
  20. modified the milestones: 10.0.0, 9.0.5 on Nov 5, 2025
  21. added a commit that references this issue on Nov 5, 2025
    ed512e5
  22. added 2 commits that reference this issue on Nov 5, 2025
    04304d6
    a5bede5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions