Domain socket handles (#1922)

* UvPipeHandle.Open(IntPtr) and underlying interop
* LibuvConstants.ENOTSUP
* IEndpointInformation.HandleType along with ListenOptions extra ctor and handle type re-specification
* Exception-based auto-detection of socket type in Listener, accept socket creation support for detected handle types in ListenerContext
* Added systemd Unix socket activation tests
This commit is contained in:
Aristarkh Zagorodnikov 2017-07-03 21:11:23 +03:00 committed by Stephen Halter
parent e9ffcdb414
commit 00d17dea79
11 changed files with 248 additions and 50 deletions

View File

@ -16,6 +16,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public class ListenOptions : IEndPointInformation
{
private FileHandleType _handleType;
internal ListenOptions(IPEndPoint endPoint)
{
Type = ListenType.IPEndPoint;
@ -29,9 +31,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
}
internal ListenOptions(ulong fileHandle)
: this(fileHandle, FileHandleType.Auto)
{
}
internal ListenOptions(ulong fileHandle, FileHandleType handleType)
{
Type = ListenType.FileHandle;
FileHandle = fileHandle;
switch (handleType)
{
case FileHandleType.Auto:
case FileHandleType.Tcp:
case FileHandleType.Pipe:
_handleType = handleType;
break;
default:
throw new NotSupportedException();
}
}
/// <summary>
@ -39,6 +56,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
/// </summary>
public ListenType Type { get; }
public FileHandleType HandleType
{
get => _handleType;
set
{
if (value == _handleType)
{
return;
}
if (Type != ListenType.FileHandle || _handleType != FileHandleType.Auto)
{
throw new InvalidOperationException();
}
switch (value)
{
case FileHandleType.Tcp:
case FileHandleType.Pipe:
_handleType = value;
break;
default:
throw new ArgumentException(nameof(HandleType));
}
}
}
// IPEndPoint is mutable so port 0 can be updated to the bound port.
/// <summary>
/// The <see cref="IPEndPoint"/> to bind to.

View File

@ -0,0 +1,15 @@
// 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.
namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
{
/// <summary>
/// Enumerates the <see cref="IEndPointInformation.FileHandle"/> types.
/// </summary>
public enum FileHandleType
{
Auto,
Tcp,
Pipe
}
}

View File

@ -31,6 +31,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
/// </summary>
ulong FileHandle { get; }
// HandleType is mutable so it can be re-specified later.
/// <summary>
/// The type of file descriptor being used.
/// Only set if <see cref="Type"/> is <see cref="ListenType.FileHandle"/>.
/// </summary>
FileHandleType HandleType { get; set; }
/// <summary>
/// Set to false to enable Nagle's algorithm for all connections.
/// </summary>

View File

@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
public const int EOF = -4095;
public static readonly int? ECONNRESET = GetECONNRESET();
public static readonly int? EADDRINUSE = GetEADDRINUSE();
public static readonly int? ENOTSUP = GetENOTSUP();
private static int? GetECONNRESET()
{
@ -46,5 +47,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
}
return null;
}
private static int? GetENOTSUP()
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
{
return -95;
}
else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
{
return -45;
}
return null;
}
}
}

View File

@ -46,53 +46,103 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
switch (EndPointInformation.Type)
{
case ListenType.IPEndPoint:
case ListenType.FileHandle:
var socket = new UvTcpHandle(Log);
try
{
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.NoDelay(EndPointInformation.NoDelay);
if (EndPointInformation.Type == ListenType.IPEndPoint)
{
socket.Bind(EndPointInformation.IPEndPoint);
// If requested port was "0", replace with assigned dynamic port.
EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint();
}
else
{
socket.Open((IntPtr)EndPointInformation.FileHandle);
}
}
catch
{
socket.Dispose();
throw;
}
return socket;
return ListenTcp(useFileHandle: false);
case ListenType.SocketPath:
var pipe = new UvPipeHandle(Log);
try
{
pipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
pipe.Bind(EndPointInformation.SocketPath);
}
catch
{
pipe.Dispose();
throw;
}
return pipe;
return ListenPipe(useFileHandle: false);
case ListenType.FileHandle:
return ListenHandle();
default:
throw new NotSupportedException();
}
}
private UvTcpHandle ListenTcp(bool useFileHandle)
{
var socket = new UvTcpHandle(Log);
try
{
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.NoDelay(EndPointInformation.NoDelay);
if (!useFileHandle)
{
socket.Bind(EndPointInformation.IPEndPoint);
// If requested port was "0", replace with assigned dynamic port.
EndPointInformation.IPEndPoint = socket.GetSockIPEndPoint();
}
else
{
socket.Open((IntPtr)EndPointInformation.FileHandle);
}
}
catch
{
socket.Dispose();
throw;
}
return socket;
}
private UvPipeHandle ListenPipe(bool useFileHandle)
{
var pipe = new UvPipeHandle(Log);
try
{
pipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
if (!useFileHandle)
{
pipe.Bind(EndPointInformation.SocketPath);
}
else
{
pipe.Open((IntPtr)EndPointInformation.FileHandle);
}
}
catch
{
pipe.Dispose();
throw;
}
return pipe;
}
private UvStreamHandle ListenHandle()
{
switch (EndPointInformation.HandleType)
{
case FileHandleType.Auto:
break;
case FileHandleType.Tcp:
return ListenTcp(useFileHandle: true);
case FileHandleType.Pipe:
return ListenPipe(useFileHandle: true);
default:
throw new NotSupportedException();
}
UvStreamHandle handle;
try
{
handle = ListenTcp(useFileHandle: true);
EndPointInformation.HandleType = FileHandleType.Tcp;
return handle;
}
catch (UvException exception) when (exception.StatusCode == LibuvConstants.ENOTSUP)
{
Log.LogDebug(0, exception, "Listener.ListenHandle");
}
handle = ListenPipe(useFileHandle: true);
EndPointInformation.HandleType = FileHandleType.Pipe;
return handle;
}
private static void ConnectionCallback(UvStreamHandle stream, int status, UvException error, object state)
{
var listener = (Listener)state;

View File

@ -28,18 +28,64 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
switch (EndPointInformation.Type)
{
case ListenType.IPEndPoint:
case ListenType.FileHandle:
var tcpHandle = new UvTcpHandle(TransportContext.Log);
tcpHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
tcpHandle.NoDelay(EndPointInformation.NoDelay);
return tcpHandle;
return AcceptTcp();
case ListenType.SocketPath:
var pipeHandle = new UvPipeHandle(TransportContext.Log);
pipeHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
return pipeHandle;
return AcceptPipe();
case ListenType.FileHandle:
return AcceptHandle();
default:
throw new InvalidOperationException();
}
}
private UvTcpHandle AcceptTcp()
{
var socket = new UvTcpHandle(TransportContext.Log);
try
{
socket.Init(Thread.Loop, Thread.QueueCloseHandle);
socket.NoDelay(EndPointInformation.NoDelay);
}
catch
{
socket.Dispose();
throw;
}
return socket;
}
private UvPipeHandle AcceptPipe()
{
var pipe = new UvPipeHandle(TransportContext.Log);
try
{
pipe.Init(Thread.Loop, Thread.QueueCloseHandle);
}
catch
{
pipe.Dispose();
throw;
}
return pipe;
}
private UvStreamHandle AcceptHandle()
{
switch (EndPointInformation.HandleType)
{
case FileHandleType.Auto:
throw new InvalidOperationException("Cannot accept on a non-specific file handle, listen should be performed first.");
case FileHandleType.Tcp:
return AcceptTcp();
case FileHandleType.Pipe:
return AcceptPipe();
default:
throw new NotSupportedException();
}
}
}
}

View File

@ -30,6 +30,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin
_uv_tcp_nodelay = NativeMethods.uv_tcp_nodelay;
_uv_pipe_init = NativeMethods.uv_pipe_init;
_uv_pipe_bind = NativeMethods.uv_pipe_bind;
_uv_pipe_open = NativeMethods.uv_pipe_open;
_uv_listen = NativeMethods.uv_listen;
_uv_accept = NativeMethods.uv_accept;
_uv_pipe_connect = NativeMethods.uv_pipe_connect;
@ -229,6 +230,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin
ThrowIfErrored(_uv_pipe_bind(handle, name));
}
protected Func<UvPipeHandle, IntPtr, int> _uv_pipe_open;
public void pipe_open(UvPipeHandle handle, IntPtr hSocket)
{
handle.Validate();
ThrowIfErrored(_uv_pipe_open(handle, hSocket));
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate void uv_connection_cb(IntPtr server, int status);
protected Func<UvStreamHandle, int, uv_connection_cb, int> _uv_listen;
@ -534,6 +542,9 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_pipe_bind(UvPipeHandle loop, string name);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_pipe_open(UvPipeHandle handle, IntPtr hSocket);
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
public static extern int uv_listen(UvStreamHandle handle, int backlog, uv_connection_cb cb);

View File

@ -21,6 +21,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal.Networkin
_uv.pipe_init(loop, this, ipc);
}
public void Open(IntPtr fileDescriptor)
{
_uv.pipe_open(this, fileDescriptor);
}
public void Bind(string name)
{
_uv.pipe_bind(this, name);

View File

@ -15,7 +15,7 @@ RUN ["ln", "-s", "/usr/share/dotnet/dotnet", "/usr/bin/dotnet"]
ADD publish/ /publish/
# Expose target ports.
EXPOSE 8080 8081 8082
EXPOSE 8080 8081 8082 8083 8084 8085
# Set entrypoint.
COPY ./docker-entrypoint.sh /

View File

@ -6,5 +6,9 @@ cd /publish
systemd-socket-activate -l 8080 -E BASE_PORT=7000 dotnet SampleApp.dll &
socat TCP-LISTEN:8081,fork TCP-CONNECT:127.0.0.1:7000 &
socat TCP-LISTEN:8082,fork TCP-CONNECT:127.0.0.1:7001 &
systemd-socket-activate -l /tmp/activate-kestrel.sock -E BASE_PORT=7100 dotnet SampleApp.dll &
socat TCP-LISTEN:8083,fork UNIX-CLIENT:/tmp/activate-kestrel.sock &
socat TCP-LISTEN:8084,fork TCP-CONNECT:127.0.0.1:7100 &
socat TCP-LISTEN:8085,fork TCP-CONNECT:127.0.0.1:7101 &
trap 'exit 0' SIGTERM
wait

View File

@ -15,6 +15,9 @@ for i in {1..10}; do
curl -f http://$(docker port $container 8080/tcp) \
&& curl -f http://$(docker port $container 8081/tcp) \
&& curl -fk https://$(docker port $container 8082/tcp) \
&& curl -f http://$(docker port $container 8083/tcp) \
&& curl -f http://$(docker port $container 8084/tcp) \
&& curl -fk https://$(docker port $container 8085/tcp) \
&& exit 0 || sleep 1;
done