FubarDevelopment / FtpServer

Portable FTP server written in .NET
http://fubardevelopment.github.io/FtpServer/
MIT License
477 stars 163 forks source link

The server committed a protocol violation #82

Closed twodogmbh closed 4 years ago

twodogmbh commented 5 years ago

When multiple connections are opened in parallel, the client terminates with the following exception:

System.Net.WebException: The underlying connection was closed: The server committed a protocol violation.
 at System.Net.FtpWebRequest.DataStreamClosed(CloseExState closeState)
at System.Net.FtpDataStream.System.Net.ICloseEx.CloseEx(CloseExState closeState)
at System.Net.FtpDataStream.Dispose(Boolean disposing)
at System.IO.Stream.Close()
at System.Net.FtpDataStream.Read(Byte[] buffer, Int32 offset, Int32 size)
at System.IO.StreamReader.ReadBuffer()
at System.IO.StreamReader.ReadLine()

This occurs with the default FTP server configuration as described in the example: https://github.com/FubarDevelopment/FtpServer

The issue can be reproduced with the following test:

    [TestMethod]
    public async Task GetFilesInParallel()
    {
        var tasks = new List<Task>();
        for(var i = 0; i < 100; i++)
        {
            var task = Task.Run(this.GetFilesAsync);
            tasks.Add(task);
        }

        await Task.WhenAll(tasks).ConfigureAwait(false);
    }

    private async Task<IList<string>> GetFilesAsync()
    {
        var requestUri = "ftp://localhost";
        var request = (FtpWebRequest)WebRequest.Create(requestUri);
        request.Method = WebRequestMethods.Ftp.ListDirectory;
        request.Credentials = new NetworkCredential("anonymous", "foo@bar.com");
        request.KeepAlive = false;

        var files = new List<string>();
        using (var response = await request.GetResponseAsync().ConfigureAwait(false))
        {
            using (var responseStream = response.GetResponseStream())
            {
                using (var reader = new StreamReader(responseStream))
                {
                    string line;
                    while ((line = reader.ReadLine()) != null)
                    {
                        files.Add(Path.GetFileName(line));
                    }

                    reader.Close();
                    responseStream.Close();
                }
            }
        }

        return files;
    }

When the KeepAlive property is set to "true" the test passes.

It seems like, when the connection is closed, the server terminates the connection too fast, before the response is sent.

For the ListDirectory command it's not a big issue, as we could just re-try, but for the DeleteFile command it's not that easy as the second re-try then returns error code 503.

The response.StatusProperty contains "250 Closing data connection."

twodogmbh commented 5 years ago

I hope you could reproduce the issue with your new unit test. Otherwise I can provide further information if required. Do you already have a possible release date with this issue fixed, as this is quite a cumbersome issue for us. Thanks.

fubar-coder commented 5 years ago

I could reproduce the issue, but I don't have an idea (yet) how to fix this problem.

fubar-coder commented 5 years ago

Progress report: It's not a TCP issue. It's a problem with the FTP protocol where the data gets sent before the 150 Opening data connection. response.

EDIT: Two possible solutions:

  1. Create an FTP response that sends the data through the data connection
  2. Create a server command that sends the data

I believe that solution 2 might be the correct solution.

fubar-coder commented 4 years ago

3.1.1 is published and should be available soon (after the validation on nuget.org). The unit test works now and your problem is (hopefully) fixed.

twodogmbh commented 4 years ago

In the release 3.1.1 the exception does not occur anymore. Thank you very much for the quick response!