Better filter clients connecting to Kestrel's dispatch pipes

This commit is contained in:
Stephen Halter 2016-11-11 14:51:37 -08:00
parent ec89197ecb
commit f1d0fafaa4
5 changed files with 109 additions and 19 deletions

View File

@ -21,6 +21,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>(); private readonly List<UvPipeHandle> _dispatchPipes = new List<UvPipeHandle>();
private int _dispatchIndex; private int _dispatchIndex;
private string _pipeName; private string _pipeName;
private byte[] _pipeMessage;
private IntPtr _fileCompletionInfoPtr; private IntPtr _fileCompletionInfoPtr;
private bool _tryDetachFromIOCP = PlatformApis.IsWindows; private bool _tryDetachFromIOCP = PlatformApis.IsWindows;
@ -36,10 +37,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public async Task StartAsync( public async Task StartAsync(
string pipeName, string pipeName,
byte[] pipeMessage,
ServerAddress address, ServerAddress address,
KestrelThread thread) KestrelThread thread)
{ {
_pipeName = pipeName; _pipeName = pipeName;
_pipeMessage = pipeMessage;
if (_fileCompletionInfoPtr == IntPtr.Zero) if (_fileCompletionInfoPtr == IntPtr.Zero)
{ {
@ -187,7 +190,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
private class PipeReadContext private class PipeReadContext
{ {
private const int _bufferLength = 16;
private readonly ListenerPrimary _listener; private readonly ListenerPrimary _listener;
private readonly byte[] _buf = new byte[_bufferLength];
private readonly IntPtr _bufPtr; private readonly IntPtr _bufPtr;
private GCHandle _bufHandle; private GCHandle _bufHandle;
private int _bytesRead; private int _bytesRead;
@ -195,16 +201,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public PipeReadContext(ListenerPrimary listener) public PipeReadContext(ListenerPrimary listener)
{ {
_listener = listener; _listener = listener;
_bufHandle = GCHandle.Alloc(new byte[8], GCHandleType.Pinned); _bufHandle = GCHandle.Alloc(_buf, GCHandleType.Pinned);
_bufPtr = _bufHandle.AddrOfPinnedObject(); _bufPtr = _bufHandle.AddrOfPinnedObject();
} }
public Libuv.uv_buf_t AllocCallback(UvStreamHandle dispatchPipe, int suggestedSize) 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 try
{ {
@ -212,9 +218,19 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
_bytesRead += 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); _listener._dispatchPipes.Add((UvPipeHandle) dispatchPipe);
dispatchPipe.ReadStop(); dispatchPipe.ReadStop();

View File

@ -17,10 +17,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
/// </summary> /// </summary>
public abstract class ListenerSecondary : ListenerContext, IAsyncDisposable public abstract class ListenerSecondary : ListenerContext, IAsyncDisposable
{ {
private static ArraySegment<ArraySegment<byte>> _pipeMessage =
new ArraySegment<ArraySegment<byte>>(new[] { new ArraySegment<byte>(BitConverter.GetBytes(Constants.PipeMessage)) });
private string _pipeName; private string _pipeName;
private byte[] _pipeMessage;
private IntPtr _ptr; private IntPtr _ptr;
private Libuv.uv_buf_t _buf; private Libuv.uv_buf_t _buf;
private bool _closed; private bool _closed;
@ -36,10 +34,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
public Task StartAsync( public Task StartAsync(
string pipeName, string pipeName,
byte[] pipeMessage,
ServerAddress address, ServerAddress address,
KestrelThread thread) KestrelThread thread)
{ {
_pipeName = pipeName; _pipeName = pipeName;
_pipeMessage = pipeMessage;
_buf = thread.Loop.Libuv.buf_init(_ptr, 4); _buf = thread.Loop.Libuv.buf_init(_ptr, 4);
ServerAddress = address; ServerAddress = address;
@ -104,7 +104,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
writeReq.Init(Thread.Loop); writeReq.Init(Thread.Loop);
writeReq.Write( writeReq.Write(
DispatchPipe, DispatchPipe,
_pipeMessage, new ArraySegment<ArraySegment<byte>>(new [] { new ArraySegment<byte>(_pipeMessage) }),
(req, status2, ex, state) => (req, status2, ex, state) =>
{ {
req.Dispose(); req.Dispose();

View File

@ -20,9 +20,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure
public const string ServerName = "Kestrel"; public const string ServerName = "Kestrel";
// "Kestrel\0"
public const ulong PipeMessage = 0x006C65727473654B;
private static int? GetECONNRESET() private static int? GetECONNRESET()
{ {
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))

View File

@ -70,6 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
try try
{ {
var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeName = (Libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
var pipeMessage = Guid.NewGuid().ToByteArray();
var single = Threads.Count == 1; var single = Threads.Count == 1;
var first = true; var first = true;
@ -91,7 +92,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
: new TcpListenerPrimary(ServiceContext); : new TcpListenerPrimary(ServiceContext);
listeners.Add(listener); listeners.Add(listener);
listener.StartAsync(pipeName, address, thread).Wait(); listener.StartAsync(pipeName, pipeMessage, address, thread).Wait();
} }
else else
{ {
@ -99,7 +100,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal
? (ListenerSecondary) new PipeListenerSecondary(ServiceContext) ? (ListenerSecondary) new PipeListenerSecondary(ServiceContext)
: new TcpListenerSecondary(ServiceContext); : new TcpListenerSecondary(ServiceContext);
listeners.Add(listener); listeners.Add(listener);
listener.StartAsync(pipeName, address, thread).Wait(); listener.StartAsync(pipeName, pipeMessage, address, thread).Wait();
} }
first = false; first = false;

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.IO;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Hosting.Server;
@ -10,6 +11,7 @@ using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel;
using Microsoft.AspNetCore.Server.Kestrel.Internal; using Microsoft.AspNetCore.Server.Kestrel.Internal;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Http; using Microsoft.AspNetCore.Server.Kestrel.Internal.Http;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking; using Microsoft.AspNetCore.Server.Kestrel.Internal.Networking;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -55,12 +57,13 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
var address = ServerAddress.FromUrl("http://127.0.0.1:0/"); var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
var pipeMessage = Guid.NewGuid().ToByteArray();
// Start primary listener // Start primary listener
var kestrelThreadPrimary = new KestrelThread(kestrelEngine); var kestrelThreadPrimary = new KestrelThread(kestrelEngine);
await kestrelThreadPrimary.StartAsync(); await kestrelThreadPrimary.StartAsync();
var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary); 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 // Until a secondary listener is added, TCP connections get dispatched directly
Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString())); Assert.Equal("Primary", await HttpClientSlim.GetStringAsync(address.ToString()));
@ -70,7 +73,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
var kestrelThreadSecondary = new KestrelThread(kestrelEngine); var kestrelThreadSecondary = new KestrelThread(kestrelEngine);
await kestrelThreadSecondary.StartAsync(); await kestrelThreadSecondary.StartAsync();
var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary); 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 // Once a secondary listener is added, TCP connections start getting dispatched to it
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString())); Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
@ -128,18 +131,19 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
{ {
var address = ServerAddress.FromUrl("http://127.0.0.1:0/"); var address = ServerAddress.FromUrl("http://127.0.0.1:0/");
var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n"); var pipeName = (libuv.IsWindows ? @"\\.\pipe\kestrel_" : "/tmp/kestrel_") + Guid.NewGuid().ToString("n");
var pipeMessage = Guid.NewGuid().ToByteArray();
// Start primary listener // Start primary listener
var kestrelThreadPrimary = new KestrelThread(kestrelEngine); var kestrelThreadPrimary = new KestrelThread(kestrelEngine);
await kestrelThreadPrimary.StartAsync(); await kestrelThreadPrimary.StartAsync();
var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary); var listenerPrimary = new TcpListenerPrimary(serviceContextPrimary);
await listenerPrimary.StartAsync(pipeName, address, kestrelThreadPrimary); await listenerPrimary.StartAsync(pipeName, pipeMessage, address, kestrelThreadPrimary);
// Add secondary listener // Add secondary listener
var kestrelThreadSecondary = new KestrelThread(kestrelEngine); var kestrelThreadSecondary = new KestrelThread(kestrelEngine);
await kestrelThreadSecondary.StartAsync(); await kestrelThreadSecondary.StartAsync();
var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary); var listenerSecondary = new TcpListenerSecondary(serviceContextSecondary);
await listenerSecondary.StartAsync(pipeName, address, kestrelThreadSecondary); await listenerSecondary.StartAsync(pipeName, pipeMessage, address, kestrelThreadSecondary);
// TCP Connections get round-robined // TCP Connections get round-robined
Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString())); Assert.Equal("Secondary", await HttpClientSlim.GetStringAsync(address.ToString()));
@ -199,7 +203,79 @@ namespace Microsoft.AspNetCore.Server.KestrelTests
Assert.Equal(1, primaryTrace.Logger.TotalErrorsLogged); Assert.Equal(1, primaryTrace.Logger.TotalErrorsLogged);
var errorMessage = primaryTrace.Logger.Messages.First(m => m.LogLevel == LogLevel.Error); var errorMessage = primaryTrace.Logger.Messages.First(m => m.LogLevel == LogLevel.Error);
Assert.Contains("EOF", errorMessage.Exception.ToString()); Assert.Equal(Constants.EOF, Assert.IsType<UvException>(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<DefaultHttpContext>(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<DefaultHttpContext>(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<IOException>(errorMessage.Exception);
Assert.Contains("Bad data", errorMessage.Exception.ToString());
} }
private class TestApplication : IHttpApplication<DefaultHttpContext> private class TestApplication : IHttpApplication<DefaultHttpContext>