Better filter clients connecting to Kestrel's dispatch pipes
This commit is contained in:
parent
ec89197ecb
commit
f1d0fafaa4
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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();
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue