diff --git a/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs b/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs index 8625350497..ad5beb9d4f 100644 --- a/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs +++ b/src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs @@ -27,6 +27,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_stop = NativeDarwinMonoMethods.uv_stop; _uv_ref = NativeDarwinMonoMethods.uv_ref; _uv_unref = NativeDarwinMonoMethods.uv_unref; + _uv_fileno = NativeDarwinMonoMethods.uv_fileno; _uv_close = NativeDarwinMonoMethods.uv_close; _uv_async_init = NativeDarwinMonoMethods.uv_async_init; _uv_async_send = NativeDarwinMonoMethods.uv_async_send; @@ -69,6 +70,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_stop = NativeMethods.uv_stop; _uv_ref = NativeMethods.uv_ref; _uv_unref = NativeMethods.uv_unref; + _uv_fileno = NativeMethods.uv_fileno; _uv_close = NativeMethods.uv_close; _uv_async_init = NativeMethods.uv_async_init; _uv_async_send = NativeMethods.uv_async_send; @@ -179,6 +181,15 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking _uv_unref(handle); } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] + protected delegate int uv_fileno_func(UvHandle handle, ref IntPtr socket); + protected uv_fileno_func _uv_fileno; + public int uv_fileno(UvHandle handle, ref IntPtr socket) + { + handle.Validate(); + return Check(_uv_fileno(handle, ref socket)); + } + [UnmanagedFunctionPointer(CallingConvention.Cdecl)] public delegate void uv_close_cb(IntPtr handle); protected Action _uv_close; @@ -229,6 +240,40 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking { handle.Validate(); Check(_uv_tcp_bind(handle, ref addr, flags)); + if (PlatformApis.IsWindows) + { + tcp_bind_windows_extras(handle); + } + } + + private unsafe void tcp_bind_windows_extras(UvTcpHandle handle) + { + const int SIO_LOOPBACK_FAST_PATH = -1744830448; // IOC_IN | IOC_WS2 | 16; + const int WSAEOPNOTSUPP = 10000 + 45; // (WSABASEERR+45) + const int SOCKET_ERROR = -1; + + var socket = IntPtr.Zero; + Check(_uv_fileno(handle, ref socket)); + + // Enable loopback fast-path for lower latency for localhost comms, like HttpPlatformHandler fronting + // http://blogs.technet.com/b/wincat/archive/2012/12/05/fast-tcp-loopback-performance-and-low-latency-with-windows-server-2012-tcp-loopback-fast-path.aspx + // https://github.com/libuv/libuv/issues/489 + var optionValue = 1; + uint dwBytes = 0u; + + var result = NativeMethods.WSAIoctl(socket, SIO_LOOPBACK_FAST_PATH, &optionValue, sizeof(int), null, 0, out dwBytes, IntPtr.Zero, IntPtr.Zero); + if (result == SOCKET_ERROR) + { + var errorId = NativeMethods.WSAGetLastError(); + if (errorId == WSAEOPNOTSUPP) + { + // This system is not >= Windows Server 2012, and the call is not supported. + } + else + { + Check(errorId); + } + } } protected Func _uv_tcp_open; @@ -509,6 +554,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_unref(UvHandle handle); + [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] + public static extern int uv_fileno(UvHandle handle, ref IntPtr socket); + [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_close(IntPtr handle, uv_close_cb close_cb); @@ -598,6 +646,22 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("libuv", CallingConvention = CallingConvention.Cdecl)] unsafe public static extern int uv_walk(UvLoopHandle loop, uv_walk_cb walk_cb, IntPtr arg); + + [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)] + unsafe public static extern int WSAIoctl( + IntPtr socket, + int dwIoControlCode, + int* lpvInBuffer, + uint cbInBuffer, + int* lpvOutBuffer, + int cbOutBuffer, + out uint lpcbBytesReturned, + IntPtr lpOverlapped, + IntPtr lpCompletionRoutine + ); + + [DllImport("WS2_32.dll", CallingConvention = CallingConvention.Winapi)] + public static extern int WSAGetLastError(); } private static class NativeDarwinMonoMethods @@ -620,6 +684,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Networking [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_unref(UvHandle handle); + [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] + public static extern int uv_fileno(UvHandle handle, ref IntPtr socket); + [DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)] public static extern void uv_close(IntPtr handle, uv_close_cb close_cb); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs index 0cc82366b2..cdeeccca4f 100644 --- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/RequestTests.cs @@ -152,9 +152,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests { host.Start(); - using (var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + using (var socket = TestConnection.CreateConnectedLoopbackSocket(port)) { - socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); socket.Send(Encoding.ASCII.GetBytes("GET /%41%CC%8A/A/../B/%41%CC%8A HTTP/1.1\r\n\r\n")); socket.Shutdown(SocketShutdown.Send); diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestConnection.cs b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestConnection.cs new file mode 100644 index 0000000000..db602658f0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/TestConnection.cs @@ -0,0 +1,35 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Sockets; +using Microsoft.AspNetCore.Server.Kestrel.Networking; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class TestConnection + { + public static Socket CreateConnectedLoopbackSocket(int port) + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = -1744830448; + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); + return socket; + } + } +} diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs index 6e9fb671e4..51b2cae261 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs @@ -14,6 +14,8 @@ using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.Kestrel; using Microsoft.AspNetCore.Server.Kestrel.Filter; using Microsoft.AspNetCore.Server.Kestrel.Infrastructure; +using Microsoft.AspNetCore.Server.Kestrel.Networking; +using Microsoft.AspNetCore.Testing.xunit; using Microsoft.Extensions.Logging; using Xunit; @@ -109,8 +111,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var address = ServerAddress.FromUrl($"http://localhost:{port}/"); var started = engine.CreateServer(address); - var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); + var socket = TestConnection.CreateConnectedLoopbackSocket(port); socket.Send(Encoding.ASCII.GetBytes("POST / HTTP/1.0\r\n\r\nHello World")); socket.Shutdown(SocketShutdown.Send); var buffer = new byte[8192]; @@ -456,8 +457,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { using (var server = new TestServer(App, testContext)) { - var socket = new Socket(SocketType.Stream, ProtocolType.Tcp); - socket.Connect(IPAddress.Loopback, server.Port); + var socket = TestConnection.CreateConnectedLoopbackSocket(server.Port); await Task.Delay(200); socket.Dispose(); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs index f75079f57e..790d654fca 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/MultipleLoopTests.cs @@ -127,6 +127,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { var pipeName = @"\\.\pipe\ServerPipeDispatchConnections" + Guid.NewGuid().ToString("n"); + var port = TestServer.GetNextPort(); var loop = new UvLoopHandle(_logger); loop.Init(_uv); @@ -156,7 +157,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var serverListenTcp = new UvTcpHandle(_logger); serverListenTcp.Init(loop, (a, b) => { }); - var address = ServerAddress.FromUrl("http://localhost:54321/"); + var address = ServerAddress.FromUrl($"http://localhost:{port}/"); serverListenTcp.Bind(address); serverListenTcp.Listen(128, (_1, status, error, _2) => { @@ -237,8 +238,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests { serverConnectionPipeAcceptedEvent.WaitOne(); - var socket = new Socket(SocketType.Stream, ProtocolType.IP); - socket.Connect(IPAddress.Loopback, 54321); + var socket = TestConnection.CreateConnectedLoopbackSocket(port); socket.Send(new byte[] { 6, 7, 8, 9 }); socket.Shutdown(SocketShutdown.Send); var cb = socket.Receive(new byte[64]); diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs index 7e05b5a533..4a438f5c78 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs @@ -87,22 +87,9 @@ namespace Microsoft.AspNetCore.Server.KestrelTests tcp2.Dispose(); stream.Dispose(); }, null); - var t = Task.Run(async () => + var t = Task.Run(() => { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); -#if DNX451 - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, port), - null, - TaskCreationOptions.None); -#else - await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port)); -#endif + var socket = TestConnection.CreateConnectedLoopbackSocket(port); socket.Dispose(); }); loop.Run(); @@ -141,17 +128,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests }, null); var t = Task.Run(async () => { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); + var socket = TestConnection.CreateConnectedLoopbackSocket(port); #if DNX451 - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, port), - null, - TaskCreationOptions.None); await Task.Factory.FromAsync( socket.BeginSend, socket.EndSend, @@ -160,7 +138,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests null, TaskCreationOptions.None); #else - await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port)); await socket.SendAsync(new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, SocketFlags.None); #endif @@ -226,17 +203,8 @@ namespace Microsoft.AspNetCore.Server.KestrelTests }, null); var t = Task.Run(async () => { - var socket = new Socket( - AddressFamily.InterNetwork, - SocketType.Stream, - ProtocolType.Tcp); + var socket = TestConnection.CreateConnectedLoopbackSocket(port); #if DNX451 - await Task.Factory.FromAsync( - socket.BeginConnect, - socket.EndConnect, - new IPEndPoint(IPAddress.Loopback, port), - null, - TaskCreationOptions.None); await Task.Factory.FromAsync( socket.BeginSend, socket.EndSend, @@ -245,7 +213,6 @@ namespace Microsoft.AspNetCore.Server.KestrelTests null, TaskCreationOptions.None); #else - await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port)); await socket.SendAsync(new[] { new ArraySegment(new byte[] { 1, 2, 3, 4, 5 }) }, SocketFlags.None); #endif diff --git a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs index 5b6e857fd0..f7f3e36b4e 100644 --- a/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs +++ b/test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs @@ -8,6 +8,7 @@ using System.Net; using System.Net.Sockets; using System.Text; using System.Threading.Tasks; +using Microsoft.AspNetCore.Server.Kestrel.Networking; using Xunit; namespace Microsoft.AspNetCore.Server.KestrelTests @@ -28,8 +29,7 @@ namespace Microsoft.AspNetCore.Server.KestrelTests public void Create(int port) { - _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - _socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); + _socket = CreateConnectedLoopbackSocket(port); _stream = new NetworkStream(_socket, false); _reader = new StreamReader(_stream, Encoding.ASCII); @@ -125,5 +125,27 @@ namespace Microsoft.AspNetCore.Server.KestrelTests var text = new string(ch, 0, count); Assert.Equal("", text); } + + public static Socket CreateConnectedLoopbackSocket(int port) + { + var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); + if (PlatformApis.IsWindows) + { + const int SIO_LOOPBACK_FAST_PATH = -1744830448; + var optionInValue = BitConverter.GetBytes(1); + try + { + socket.IOControl(SIO_LOOPBACK_FAST_PATH, optionInValue, null); + } + catch + { + // If the operating system version on this machine did + // not support SIO_LOOPBACK_FAST_PATH (i.e. version + // prior to Windows 8 / Windows Server 2012), handle the exception + } + } + socket.Connect(new IPEndPoint(IPAddress.Loopback, port)); + return socket; + } } }