Skip to content
This repository was archived by the owner on Dec 18, 2018. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions src/Microsoft.AspNetCore.Server.Kestrel/Networking/Libuv.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ public Libuv()
_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;
Expand Down Expand Up @@ -68,6 +69,7 @@ public Libuv()
_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;
Expand Down Expand Up @@ -177,6 +179,15 @@ public void unref(UvHandle handle)
_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<IntPtr, uv_close_cb> _uv_close;
Expand Down Expand Up @@ -221,6 +232,40 @@ public void tcp_bind(UvTcpHandle handle, ref SockAddr addr, int flags)
{
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<UvTcpHandle, IntPtr, int> _uv_tcp_open;
Expand Down Expand Up @@ -501,6 +546,9 @@ private static class NativeMethods
[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);

Expand Down Expand Up @@ -587,6 +635,22 @@ private static class NativeMethods

[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
Expand All @@ -609,6 +673,9 @@ private static class NativeDarwinMonoMethods
[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);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used on Darwin Mono so may not be needed if the feature is windows specific...

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would end up with mismatched function implements? So if someone did want to use the fileno in future calling the function on darwin mono would return incorrect info (if replaced with a dummy function)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they need it they would have to add them. I don't really care too much - the __Internal is going away soon. It won't/can't work with dotnet and even now when using dotnet the regular DllImports are being used on Mac on Mono.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean, I don't know what the uv_fileno function should resolve to on Darwin Mono if taken out.

With this in it resolves to the libuv function - if I take it out should it resolve to a function shim with body: throw new NotImplementedExecption("Libuv function not mapped on on Darwin Mono")?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you are right. It's better and easier to have uv_fileno in __Internal just as any other function than do a special dance just to avoid it.


[DllImport("__Internal", CallingConvention = CallingConvention.Cdecl)]
public static extern void uv_close(IntPtr handle, uv_close_cb close_cb);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,9 +158,8 @@ public async Task RequestPathIsNormalized()
{
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);

Expand Down
Original file line number Diff line number Diff line change
@@ -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;
}
}
}
7 changes: 3 additions & 4 deletions test/Microsoft.AspNetCore.Server.KestrelTests/EngineTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
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;
Expand Down Expand Up @@ -117,8 +118,7 @@ public void ConnectionCanReadAndWrite(TestServiceContext testContext)
var started = engine.CreateServer(address);

Console.WriteLine("Started");
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];
Expand Down Expand Up @@ -474,8 +474,7 @@ public async Task DisconnectingClient(ServiceContext testContext)
{
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();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ public void ServerPipeDispatchConnections()
{
var pipeName = @"\\.\pipe\ServerPipeDispatchConnections" + Guid.NewGuid().ToString("n");

var port = TestServer.GetNextPort();
var loop = new UvLoopHandle(_logger);
loop.Init(_uv);

Expand Down Expand Up @@ -153,7 +154,7 @@ public void ServerPipeDispatchConnections()

var serverListenTcp = new UvTcpHandle(_logger);
serverListenTcp.Init(loop);
var address = ServerAddress.FromUrl("http://localhost:54321/");
var address = ServerAddress.FromUrl($"http://localhost:{port}/");
serverListenTcp.Bind(address);
serverListenTcp.Listen(128, (_1, status, error, _2) =>
{
Expand Down Expand Up @@ -234,8 +235,7 @@ public void ServerPipeDispatchConnections()
{
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]);
Expand Down
41 changes: 4 additions & 37 deletions test/Microsoft.AspNetCore.Server.KestrelTests/NetworkingTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,9 @@ public async Task SocketCanListenAndAccept()
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();
Expand Down Expand Up @@ -143,17 +130,8 @@ public async Task SocketCanRead()
Console.WriteLine("Task.Run");
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,
Expand All @@ -162,7 +140,6 @@ await Task.Factory.FromAsync(
null,
TaskCreationOptions.None);
#else
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port));
await socket.SendAsync(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4, 5 }) },
SocketFlags.None);
#endif
Expand Down Expand Up @@ -230,17 +207,8 @@ public async Task SocketCanReadAndWrite()
Console.WriteLine("Task.Run");
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,
Expand All @@ -249,7 +217,6 @@ await Task.Factory.FromAsync(
null,
TaskCreationOptions.None);
#else
await socket.ConnectAsync(new IPEndPoint(IPAddress.Loopback, port));
await socket.SendAsync(new[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4, 5 }) },
SocketFlags.None);
#endif
Expand Down
26 changes: 24 additions & 2 deletions test/Microsoft.AspNetCore.Server.KestrelTests/TestConnection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Server.Kestrel.Networking;
using Xunit;

namespace Microsoft.AspNetCore.Server.KestrelTests
Expand All @@ -28,8 +29,7 @@ public TestConnection(int port)

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);
Expand Down Expand Up @@ -125,5 +125,27 @@ public async Task ReceiveEnd(params string[] lines)
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;
}
}
}