From f1d0fafaa48ec4e60719f21927d4634642862429 Mon Sep 17 00:00:00 2001 From: Stephen Halter Date: Fri, 11 Nov 2016 14:51:37 -0800 Subject: [PATCH] Better filter clients connecting to Kestrel's dispatch pipes --- .../Internal/Http/ListenerPrimary.cs | 26 ++++-- .../Internal/Http/ListenerSecondary.cs | 8 +- .../Internal/Infrastructure/Constants.cs | 3 - .../Internal/KestrelEngine.cs | 5 +- .../ListenerPrimaryTests.cs | 86 +++++++++++++++++-- 5 files changed, 109 insertions(+), 19 deletions(-) diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerPrimary.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerPrimary.cs index d20ea8dd8..4b192c505 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerPrimary.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerPrimary.cs @@ -21,6 +21,7 @@ public abstract class ListenerPrimary : Listener private readonly List _dispatchPipes = new List(); private int _dispatchIndex; private string _pipeName; + private byte[] _pipeMessage; private IntPtr _fileCompletionInfoPtr; private bool _tryDetachFromIOCP = PlatformApis.IsWindows; @@ -36,10 +37,12 @@ protected ListenerPrimary(ServiceContext serviceContext) : base(serviceContext) public async Task StartAsync( string pipeName, + byte[] pipeMessage, ServerAddress address, KestrelThread thread) { _pipeName = pipeName; + _pipeMessage = pipeMessage; if (_fileCompletionInfoPtr == IntPtr.Zero) { @@ -187,7 +190,10 @@ await Thread.PostAsync(state => private class PipeReadContext { + private const int _bufferLength = 16; + private readonly ListenerPrimary _listener; + private readonly byte[] _buf = new byte[_bufferLength]; private readonly IntPtr _bufPtr; private GCHandle _bufHandle; private int _bytesRead; @@ -195,16 +201,16 @@ private class PipeReadContext public PipeReadContext(ListenerPrimary listener) { _listener = listener; - _bufHandle = GCHandle.Alloc(new byte[8], GCHandleType.Pinned); + _bufHandle = GCHandle.Alloc(_buf, GCHandleType.Pinned); _bufPtr = _bufHandle.AddrOfPinnedObject(); } public Libuv.uv_buf_t AllocCallback(UvStreamHandle dispatchPipe, int suggestedSize) { - return dispatchPipe.Libuv.buf_init(_bufPtr + _bytesRead, 8 - _bytesRead); + return dispatchPipe.Libuv.buf_init(_bufPtr + _bytesRead, _bufferLength - _bytesRead); } - public unsafe void ReadCallback(UvStreamHandle dispatchPipe, int status) + public void ReadCallback(UvStreamHandle dispatchPipe, int status) { try { @@ -212,9 +218,19 @@ public unsafe void ReadCallback(UvStreamHandle dispatchPipe, int status) _bytesRead += status; - if (_bytesRead == 8) + if (_bytesRead == _bufferLength) { - if (*(ulong*)_bufPtr == Constants.PipeMessage) + var correctMessage = true; + + for (var i = 0; i < _bufferLength; i++) + { + if (_buf[i] != _listener._pipeMessage[i]) + { + correctMessage = false; + } + } + + if (correctMessage) { _listener._dispatchPipes.Add((UvPipeHandle) dispatchPipe); dispatchPipe.ReadStop(); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerSecondary.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerSecondary.cs index 9220aa4dd..078f9c6e0 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerSecondary.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Http/ListenerSecondary.cs @@ -17,10 +17,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http /// public abstract class ListenerSecondary : ListenerContext, IAsyncDisposable { - private static ArraySegment> _pipeMessage = - new ArraySegment>(new[] { new ArraySegment(BitConverter.GetBytes(Constants.PipeMessage)) }); - private string _pipeName; + private byte[] _pipeMessage; private IntPtr _ptr; private Libuv.uv_buf_t _buf; private bool _closed; @@ -36,10 +34,12 @@ protected ListenerSecondary(ServiceContext serviceContext) : base(serviceContext public Task StartAsync( string pipeName, + byte[] pipeMessage, ServerAddress address, KestrelThread thread) { _pipeName = pipeName; + _pipeMessage = pipeMessage; _buf = thread.Loop.Libuv.buf_init(_ptr, 4); ServerAddress = address; @@ -104,7 +104,7 @@ private void ConnectedCallback(UvConnectRequest connect, int status, Exception e writeReq.Init(Thread.Loop); writeReq.Write( DispatchPipe, - _pipeMessage, + new ArraySegment>(new [] { new ArraySegment(_pipeMessage) }), (req, status2, ex, state) => { req.Dispose(); diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs index 9c9fbf73a..77968bca1 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/Infrastructure/Constants.cs @@ -20,9 +20,6 @@ internal class Constants public const string ServerName = "Kestrel"; - // "Kestrel\0" - public const ulong PipeMessage = 0x006C65727473654B; - private static int? GetECONNRESET() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs index 1f7c4c7a3..3b3b46a4c 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Internal/KestrelEngine.cs @@ -70,6 +70,7 @@ public IDisposable CreateServer(ServerAddress address) try { var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); var single = Threads.Count == 1; var first = true; @@ -91,7 +92,7 @@ public IDisposable CreateServer(ServerAddress address) : new TcpListenerPrimary(ServiceContext); listeners.Add(listener); - listener.StartAsync(pipeName, address, thread).Wait(); + listener.StartAsync(pipeName, pipeMessage, address, thread).Wait(); } else { @@ -99,7 +100,7 @@ public IDisposable CreateServer(ServerAddress address) ? (ListenerSecondary) new PipeListenerSecondary(ServiceContext) : new TcpListenerSecondary(ServiceContext); listeners.Add(listener); - listener.StartAsync(pipeName, address, thread).Wait(); + listener.StartAsync(pipeName, pipeMessage, address, thread).Wait(); } first = false; diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerPrimaryTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerPrimaryTests.cs index 13be40ed2..94f57fc99 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerPrimaryTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/ListenerPrimaryTests.cs @@ -2,6 +2,7 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; +using System.IO; using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Hosting.Server; @@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; +using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Logging; @@ -55,12 +57,13 @@ public async Task ConnectionsGetRoundRobinedToSecondaryListeners() { var address = ServerAddress.FromUrl("http://127.0.0.1:0/"); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); // Start primary listener var kestrelThreadPrimary = new KestrelThread(kestrelEngine); await kestrelThreadPrimary.StartAsync(); var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary); - await listenerPrimary.StartAsync(pipeName, address, kestrelThreadPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary); // Until a secondary listener is added, TCP connections get dispatched directly Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString())); @@ -70,7 +73,7 @@ public async Task ConnectionsGetRoundRobinedToSecondaryListeners() var kestrelThreadSecondary = new KestrelThread(kestrelEngine); await kestrelThreadSecondary.StartAsync(); var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary); - await listenerSecondary.StartAsync(pipeName, address, kestrelThreadSecondary); + await listenerSecondary.StartAsync(pipeName, pipeMessage, address, kestrelThreadSecondary); // Once a secondary listener is added, TCP connections start getting dispatched to it Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString())); @@ -128,18 +131,19 @@ public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() { var address = ServerAddress.FromUrl("http://127.0.0.1:0/"); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); // Start primary listener var kestrelThreadPrimary = new KestrelThread(kestrelEngine); await kestrelThreadPrimary.StartAsync(); var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary); - await listenerPrimary.StartAsync(pipeName, address, kestrelThreadPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary); // Add secondary listener var kestrelThreadSecondary = new KestrelThread(kestrelEngine); await kestrelThreadSecondary.StartAsync(); var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary); - await listenerSecondary.StartAsync(pipeName, address, kestrelThreadSecondary); + await listenerSecondary.StartAsync(pipeName, pipeMessage, address, kestrelThreadSecondary); // TCP Connections get round-robined Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString())); @@ -199,7 +203,79 @@ public async Task NonListenerPipeConnectionsAreLoggedAndIgnored() Assert.Equal(1, primaryTrace.Logger.TotalErrorsLogged); var errorMessage = primaryTrace.Logger.Messages.First(m => m.LogLevel == LogLevel.Error); - Assert.Contains("EOF", errorMessage.Exception.ToString()); + Assert.Equal(Constants.EOF, Assert.IsType(errorMessage.Exception).StatusCode); + } + + + [Fact] + public async Task PipeConnectionsWithWrongMessageAreLoggedAndIgnored() + { + var libuv = new Libuv(); + + var primaryTrace = new TestKestrelTrace(); + + var serviceContextPrimary = new TestServiceContext + { + Log = primaryTrace, + FrameFactory = context => + { + return new Frame(new TestApplication(c => + { + return c.Response.WriteAsync("Primary"); + }), context); + } + }; + + var serviceContextSecondary = new ServiceContext + { + Log = new TestKestrelTrace(), + AppLifetime = serviceContextPrimary.AppLifetime, + DateHeaderValueManager = serviceContextPrimary.DateHeaderValueManager, + ServerOptions = serviceContextPrimary.ServerOptions, + ThreadPool = serviceContextPrimary.ThreadPool, + FrameFactory = context => + { + return new Frame(new TestApplication(c => + { + return c.Response.WriteAsync("Secondary"); ; + }), context); + } + }; + + using (var kestrelEngine = new KestrelEngine(libuv, serviceContextPrimary)) + { + var address = ServerAddress.FromUrl("http://127.0.0.1:0/"); + var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); + var pipeMessage = Guid.NewGuid().ToByteArray(); + + // Start primary listener + var kestrelThreadPrimary = new KestrelThread(kestrelEngine); + await kestrelThreadPrimary.StartAsync(); + var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary); + await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary); + + // Add secondary listener with wrong pipe message + var kestrelThreadSecondary = new KestrelThread(kestrelEngine); + await kestrelThreadSecondary.StartAsync(); + var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary); + await listenerSecondary.StartAsync(pipeName, Guid.NewGuid().ToByteArray(), address, kestrelThreadSecondary); + + // TCP Connections get round-robined + Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString())); + Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString())); + Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString())); + + await listenerSecondary.DisposeAsync(); + await kestrelThreadSecondary.StopAsync(TimeSpan.FromSeconds(1)); + + await listenerPrimary.DisposeAsync(); + await kestrelThreadPrimary.StopAsync(TimeSpan.FromSeconds(1)); + } + + Assert.Equal(1, primaryTrace.Logger.TotalErrorsLogged); + var errorMessage = primaryTrace.Logger.Messages.First(m => m.LogLevel == LogLevel.Error); + Assert.IsType(errorMessage.Exception); + Assert.Contains("Bad data", errorMessage.Exception.ToString()); } private class TestApplication : IHttpApplication