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>
|
/// </summary>
|
||||||
public class ListenOptions : IEndPointInformation
|
public class ListenOptions : IEndPointInformation
|
||||||
{
|
{
|
||||||
|
private FileHandleType _handleType;
|
||||||
|
|
||||||
internal ListenOptions(IPEndPoint endPoint)
|
internal ListenOptions(IPEndPoint endPoint)
|
||||||
{
|
{
|
||||||
Type = ListenType.IPEndPoint;
|
Type = ListenType.IPEndPoint;
|
||||||
|
|
@ -29,9 +31,24 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
}
|
}
|
||||||
|
|
||||||
internal ListenOptions(ulong fileHandle)
|
internal ListenOptions(ulong fileHandle)
|
||||||
|
: this(fileHandle, FileHandleType.Auto)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ListenOptions(ulong fileHandle, FileHandleType handleType)
|
||||||
{
|
{
|
||||||
Type = ListenType.FileHandle;
|
Type = ListenType.FileHandle;
|
||||||
FileHandle = fileHandle;
|
FileHandle = fileHandle;
|
||||||
|
switch (handleType)
|
||||||
|
{
|
||||||
|
case FileHandleType.Auto:
|
||||||
|
case FileHandleType.Tcp:
|
||||||
|
case FileHandleType.Pipe:
|
||||||
|
_handleType = handleType;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new NotSupportedException();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -39,6 +56,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public ListenType Type { get; }
|
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.
|
// IPEndPoint is mutable so port 0 can be updated to the bound port.
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="IPEndPoint"/> to bind to.
|
/// 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>
|
/// </summary>
|
||||||
ulong FileHandle { get; }
|
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>
|
/// <summary>
|
||||||
/// Set to false to enable Nagle's algorithm for all connections.
|
/// Set to false to enable Nagle's algorithm for all connections.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
public const int EOF = -4095;
|
public const int EOF = -4095;
|
||||||
public static readonly int? ECONNRESET = GetECONNRESET();
|
public static readonly int? ECONNRESET = GetECONNRESET();
|
||||||
public static readonly int? EADDRINUSE = GetEADDRINUSE();
|
public static readonly int? EADDRINUSE = GetEADDRINUSE();
|
||||||
|
public static readonly int? ENOTSUP = GetENOTSUP();
|
||||||
|
|
||||||
private static int? GetECONNRESET()
|
private static int? GetECONNRESET()
|
||||||
{
|
{
|
||||||
|
|
@ -46,5 +47,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
}
|
}
|
||||||
return null;
|
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)
|
switch (EndPointInformation.Type)
|
||||||
{
|
{
|
||||||
case ListenType.IPEndPoint:
|
case ListenType.IPEndPoint:
|
||||||
case ListenType.FileHandle:
|
return ListenTcp(useFileHandle: false);
|
||||||
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;
|
|
||||||
case ListenType.SocketPath:
|
case ListenType.SocketPath:
|
||||||
var pipe = new UvPipeHandle(Log);
|
return ListenPipe(useFileHandle: false);
|
||||||
|
case ListenType.FileHandle:
|
||||||
try
|
return ListenHandle();
|
||||||
{
|
|
||||||
pipe.Init(Thread.Loop, Thread.QueueCloseHandle, false);
|
|
||||||
pipe.Bind(EndPointInformation.SocketPath);
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
pipe.Dispose();
|
|
||||||
throw;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pipe;
|
|
||||||
default:
|
default:
|
||||||
throw new NotSupportedException();
|
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)
|
private static void ConnectionCallback(UvStreamHandle stream, int status, UvException error, object state)
|
||||||
{
|
{
|
||||||
var listener = (Listener)state;
|
var listener = (Listener)state;
|
||||||
|
|
|
||||||
|
|
@ -28,18 +28,64 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv.Internal
|
||||||
switch (EndPointInformation.Type)
|
switch (EndPointInformation.Type)
|
||||||
{
|
{
|
||||||
case ListenType.IPEndPoint:
|
case ListenType.IPEndPoint:
|
||||||
case ListenType.FileHandle:
|
return AcceptTcp();
|
||||||
var tcpHandle = new UvTcpHandle(TransportContext.Log);
|
|
||||||
tcpHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
|
|
||||||
tcpHandle.NoDelay(EndPointInformation.NoDelay);
|
|
||||||
return tcpHandle;
|
|
||||||
case ListenType.SocketPath:
|
case ListenType.SocketPath:
|
||||||
var pipeHandle = new UvPipeHandle(TransportContext.Log);
|
return AcceptPipe();
|
||||||
pipeHandle.Init(Thread.Loop, Thread.QueueCloseHandle);
|
case ListenType.FileHandle:
|
||||||
return pipeHandle;
|
return AcceptHandle();
|
||||||
default:
|
default:
|
||||||
throw new InvalidOperationException();
|
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_tcp_nodelay = NativeMethods.uv_tcp_nodelay;
|
||||||
_uv_pipe_init = NativeMethods.uv_pipe_init;
|
_uv_pipe_init = NativeMethods.uv_pipe_init;
|
||||||
_uv_pipe_bind = NativeMethods.uv_pipe_bind;
|
_uv_pipe_bind = NativeMethods.uv_pipe_bind;
|
||||||
|
_uv_pipe_open = NativeMethods.uv_pipe_open;
|
||||||
_uv_listen = NativeMethods.uv_listen;
|
_uv_listen = NativeMethods.uv_listen;
|
||||||
_uv_accept = NativeMethods.uv_accept;
|
_uv_accept = NativeMethods.uv_accept;
|
||||||
_uv_pipe_connect = NativeMethods.uv_pipe_connect;
|
_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));
|
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)]
|
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
|
||||||
public delegate void uv_connection_cb(IntPtr server, int status);
|
public delegate void uv_connection_cb(IntPtr server, int status);
|
||||||
protected Func<UvStreamHandle, int, uv_connection_cb, int> _uv_listen;
|
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)]
|
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern int uv_pipe_bind(UvPipeHandle loop, string name);
|
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)]
|
[DllImport("libuv", CallingConvention = CallingConvention.Cdecl)]
|
||||||
public static extern int uv_listen(UvStreamHandle handle, int backlog, uv_connection_cb cb);
|
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);
|
_uv.pipe_init(loop, this, ipc);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void Open(IntPtr fileDescriptor)
|
||||||
|
{
|
||||||
|
_uv.pipe_open(this, fileDescriptor);
|
||||||
|
}
|
||||||
|
|
||||||
public void Bind(string name)
|
public void Bind(string name)
|
||||||
{
|
{
|
||||||
_uv.pipe_bind(this, name);
|
_uv.pipe_bind(this, name);
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,7 @@ RUN ["ln", "-s", "/usr/share/dotnet/dotnet", "/usr/bin/dotnet"]
|
||||||
ADD publish/ /publish/
|
ADD publish/ /publish/
|
||||||
|
|
||||||
# Expose target ports.
|
# Expose target ports.
|
||||||
EXPOSE 8080 8081 8082
|
EXPOSE 8080 8081 8082 8083 8084 8085
|
||||||
|
|
||||||
# Set entrypoint.
|
# Set entrypoint.
|
||||||
COPY ./docker-entrypoint.sh /
|
COPY ./docker-entrypoint.sh /
|
||||||
|
|
|
||||||
|
|
@ -6,5 +6,9 @@ cd /publish
|
||||||
systemd-socket-activate -l 8080 -E BASE_PORT=7000 dotnet SampleApp.dll &
|
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:8081,fork TCP-CONNECT:127.0.0.1:7000 &
|
||||||
socat TCP-LISTEN:8082,fork TCP-CONNECT:127.0.0.1:7001 &
|
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
|
trap 'exit 0' SIGTERM
|
||||||
wait
|
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 8080/tcp) \
|
||||||
&& curl -f http://$(docker port $container 8081/tcp) \
|
&& curl -f http://$(docker port $container 8081/tcp) \
|
||||||
&& curl -fk https://$(docker port $container 8082/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;
|
&& exit 0 || sleep 1;
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue