web-dev-qa-db-fra.com

TLS/SSL avec System.IO.Pipelines

J'ai remarqué le nouveau System.IO.Pipelines et j'essaie de lui porter du code existant, basé sur le flux. Les problèmes liés aux flux sont bien compris, mais il comporte en même temps un riche écosystème de classes apparentées.

Dans l'exemple présenté ici, il existe un petit serveur d'écho tcp . https://blogs.msdn.Microsoft.com/dotnet/2018/07/09/system-io-pipelines-high-performance-io -in-net/

Un extrait du code est attaché ici:

    private static async Task ProcessLinesAsync(Socket socket)
    {
        Console.WriteLine($"[{socket.RemoteEndPoint}]: connected");

        var pipe = new Pipe();
        Task writing = FillPipeAsync(socket, pipe.Writer);
        Task reading = ReadPipeAsync(socket, pipe.Reader);

        await Task.WhenAll(reading, writing);

        Console.WriteLine($"[{socket.RemoteEndPoint}]: disconnected");
    }

    private static async Task FillPipeAsync(Socket socket, PipeWriter writer)
    {
        const int minimumBufferSize = 512;

        while (true)
        {
            try
            {
                // Request a minimum of 512 bytes from the PipeWriter
                Memory<byte> memory = writer.GetMemory(minimumBufferSize);

                int bytesRead = await socket.ReceiveAsync(memory, SocketFlags.None);
                if (bytesRead == 0)
                {
                    break;
                }

                // Tell the PipeWriter how much was read
                writer.Advance(bytesRead);
            }
            catch
            {
                break;
            }

            // Make the data available to the PipeReader
            FlushResult result = await writer.FlushAsync();

            if (result.IsCompleted)
            {
                break;
            }
        }

        // Signal to the reader that we're done writing
        writer.Complete();
    }

    private static async Task ReadPipeAsync(Socket socket, PipeReader reader)
    {
        while (true)
        {
            ReadResult result = await reader.ReadAsync();

            ReadOnlySequence<byte> buffer = result.Buffer;
            SequencePosition? position = null;

            do
            {
                // Find the EOL
                position = buffer.PositionOf((byte)'\n');

                if (position != null)
                {
                    var line = buffer.Slice(0, position.Value);
                    ProcessLine(socket, line);

                    // This is equivalent to position + 1
                    var next = buffer.GetPosition(1, position.Value);

                    // Skip what we've already processed including \n
                    buffer = buffer.Slice(next);
                }
            }
            while (position != null);

            // We sliced the buffer until no more data could be processed
            // Tell the PipeReader how much we consumed and how much we left to process
            reader.AdvanceTo(buffer.Start, buffer.End);

            if (result.IsCompleted)
            {
                break;
            }
        }

        reader.Complete();
    }

    private static void ProcessLine(Socket socket, in ReadOnlySequence<byte> buffer)
    {
        if (_echo)
        {
            Console.Write($"[{socket.RemoteEndPoint}]: ");
            foreach (var segment in buffer)
            {
                Console.Write(Encoding.UTF8.GetString(segment.Span));
            }
            Console.WriteLine();
        }
    }

Lorsque vous utilisez des flux, vous pouvez facilement ajouter SSL/TLS à votre code simplement en l’enveloppant dans SslStream. Comment cela est-il censé être résolu avec Pipelines?

7
agnsaft

Les canaux nommés sont un protocole réseau, tout comme HTTP, FTP et SMTP. Regardons le .net Framework pour quelques exemples rapides:

  • SSL est exploité automatiquement par les connexions HTTP, selon l’URI de base. Si l'URI apparaît avec "HTTPS:", SSL est Utilisé. 
  • SSL est exploité manuellement par les connexions FTP en définissant la propriété EnableSsl sur true avant d'appeler GetResponse ().
  • Le protocole SSL est exploité par SMTP de la même manière que FTP.

Mais que se passe-t-il si nous utilisons un protocole réseau différent, tel que des canaux? Dès le départ, nous savons que rien ne ressemble à un préfixe "HTTPS". De plus, nous pouvons lire la documentation de System.IO.Piplines et constater qu’il n’existe pas de méthode "EnableSsl". Toutefois, dans .NET Framework et .NET Core, la classe SslStream est disponible. Cette classe vous permet de créer un flux SslStream à partir de presque tous les flux disponibles. 

L'espace de noms System.IO.Pipes est également disponible dans .NET Framework et .NET Core. Les classes disponibles dans l'espace de noms Pipes sont très utiles. 

  • AnonymousPipeClientStream 
  • AnonymousPipeServerStream
  • NamedPipeClientStream 
  • NamedPipeServerStream 
  • PipeStream 

Toutes ces classes renvoient une sorte d'objet qui hérite de Stream et peut donc être utilisé dans le constructeur d'un SslStream. 

Comment cela est-il lié à l'espace de noms System.IO.Piplines? Eh bien ... ça ne va pas. Aucune des classes, structures ou interfaces définies dans l'espace de noms System.IO.Pipelines n'hérite de Stream. Nous ne pouvons donc pas utiliser directement la classe SslStream. 

Au lieu de cela, nous avons accès à PipeReaders et PipeWriters. Parfois, nous n'en avons qu'un, mais considérons un tuyau bidirectionnel pour avoir accès aux deux en même temps. 

L'espace de noms System.IO.Piplines fournit utilement une interface IDuplexPipe. Si nous voulons envelopper PipeReader et PipeWriters dans un flux SSL, nous devons définir un nouveau type qui implémente IDuplexPipe. 

Dans ce nouveau type:

  • Nous allons définir un SslStream.
  • Nous utiliserons des canaux génériques comme tampons d'entrée et de sortie. 
  • Le PipeReader utilisera le lecteur du tampon d’entrée. Nous allons utiliser ce tampon d'entrée pour extraire des données du flux SSL.
  • Le PipeWriter utilisera le rédacteur du tampon de sortie. Nous allons utiliser ce tampon de sortie pour envoyer des données au flux SSL.

Voici un exemple en pseudocode:

SslStreamDuplexPipe : IDuplexPipe
{ 
    SslStream sslStream;
    Pipe inputBuffer;
    Pipe outputBuffer;

    public PipeReader Input = inputBuffer.Reader;
    public PipeWriter Output = outputBuffer.Writer;

    ReadDataFromSslStream()
    {
        int bytes = sslStream.Read(new byte[2048], 0, 2048);
        inputBuffer.Writer.Advance(bytes)
        inputBuffer.Writer.Flush();
    }

    //and the reverse to write to the SslStream 
 }

Comme vous pouvez le constater, nous utilisons toujours la classe SslStream de l’espace de noms System.Net.Security. Cela nous a pris quelques étapes supplémentaires. 

Cela signifie-t-il que vous utilisez toujours les flux? Oui! Cependant, une fois que vous avez entièrement implémenté votre classe SslStreamDuplexPipe, vous ne pouvez utiliser que des tubes. Pas besoin d'enrouler un SslStream autour de tout. 

Marc Gravell a écrit une explication beaucoup plus détaillée à ce sujet. La première des 3 parties peut être trouvée ici: https://blog.marcgravell.com/2018/07/pipe-dreams-part-1.html

De plus, vous pouvez en savoir plus sur les différentes classes .NET mentionnées:

1
Daniel Lambert