diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs
index 5508b46afe..46bae7fba3 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Core/ListenOptions.cs
@@ -16,6 +16,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
///
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();
+ }
}
///
@@ -39,6 +56,32 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core
///
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.
///
/// The to bind to.
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/FileHandleType.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/FileHandleType.cs
new file mode 100644
index 0000000000..bb70e4ec34
--- /dev/null
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/FileHandleType.cs
@@ -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
+{
+ ///
+ /// Enumerates the types.
+ ///
+ public enum FileHandleType
+ {
+ Auto,
+ Tcp,
+ Pipe
+ }
+}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/IEndPointInformation.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/IEndPointInformation.cs
index 7f025ffe23..1b7abfa497 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/IEndPointInformation.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions/Internal/IEndPointInformation.cs
@@ -31,6 +31,13 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal
///
ulong FileHandle { get; }
+ // HandleType is mutable so it can be re-specified later.
+ ///
+ /// The type of file descriptor being used.
+ /// Only set if is .
+ ///
+ FileHandleType HandleType { get; set; }
+
///
/// Set to false to enable Nagle's algorithm for all connections.
///
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConstants.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConstants.cs
index 70424f7b0c..1e7c8f319d 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConstants.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/LibuvConstants.cs
@@ -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;
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Listener.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Listener.cs
index a4b6a8d6bb..26c6fba06a 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Listener.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Listener.cs
@@ -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;
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/ListenerContext.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/ListenerContext.cs
index 217bac0d12..37ea4eed36 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/ListenerContext.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/ListenerContext.cs
@@ -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();
+ }
+ }
}
}
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/LibuvFunctions.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/LibuvFunctions.cs
index 06e7030bef..d5452242e6 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/LibuvFunctions.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/LibuvFunctions.cs
@@ -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 _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 _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);
diff --git a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/UvPipeHandle.cs b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/UvPipeHandle.cs
index 338cc1f643..9afdb67712 100644
--- a/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/UvPipeHandle.cs
+++ b/src/Microsoft.AspNetCore.Server.Kestrel.Transport.Libuv/Internal/Networking/UvPipeHandle.cs
@@ -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);
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/Dockerfile b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/Dockerfile
index 8fd99946ee..5aee5312dd 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/Dockerfile
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/Dockerfile
@@ -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 /
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker-entrypoint.sh b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker-entrypoint.sh
index db0de9d602..c85c5fe0bd 100644
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker-entrypoint.sh
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/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
diff --git a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh
index cce561474d..eec694cdd7 100755
--- a/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh
+++ b/test/Microsoft.AspNetCore.Server.Kestrel.FunctionalTests/SystemdActivation/docker.sh
@@ -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