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:
parent
e9ffcdb414
commit
00d17dea79
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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 /
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue