From ecacf90c7f465373a45679e8439615f440eb0733 Mon Sep 17 00:00:00 2001 From: David Fowler Date: Fri, 31 May 2019 14:04:04 -0700 Subject: [PATCH] Added support for unix domain sockets to the sockets transport (#10560) - Added a test for UnixDomainSockets on both transports --- .../Kestrel/Core/src/KestrelServerOptions.cs | 4 +- .../src/SocketConnectionListener.cs | 18 +-- .../FunctionalTests/UnixDomainSocketsTests.cs | 117 ++++++++++++++++++ 3 files changed, 131 insertions(+), 8 deletions(-) create mode 100644 src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs diff --git a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs index 01a94fb0e7..7ac2acc27e 100644 --- a/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs +++ b/src/Servers/Kestrel/Core/src/KestrelServerOptions.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography.X509Certificates; @@ -295,7 +296,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core { throw new ArgumentNullException(nameof(socketPath)); } - if (socketPath.Length == 0 || socketPath[0] != '/') + + if (!Path.IsPathRooted(socketPath)) { throw new ArgumentException(CoreStrings.UnixSocketPathMustBeAbsolute, nameof(socketPath)); } diff --git a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs index b51e22e61e..6f6e10f867 100644 --- a/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.Sockets/src/SocketConnectionListener.cs @@ -31,10 +31,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets SocketTransportOptions options, ISocketsTrace trace) { - Debug.Assert(endpoint != null); - Debug.Assert(endpoint is IPEndPoint); - Debug.Assert(trace != null); - EndPoint = endpoint; _trace = trace; _options = options; @@ -66,9 +62,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets throw new InvalidOperationException(SocketsStrings.TransportAlreadyBound); } - // TODO: Add support for UnixDomainSocket + Socket listenSocket; - var listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp); + // Unix domain sockets are unspecified + var protocolType = EndPoint is UnixDomainSocketEndPoint ? ProtocolType.Unspecified : ProtocolType.Tcp; + + listenSocket = new Socket(EndPoint.AddressFamily, SocketType.Stream, protocolType); // Kestrel expects IPv6Any to bind to both IPv6 and IPv4 if (EndPoint is IPEndPoint ip && ip.Address == IPAddress.IPv6Any) @@ -99,7 +98,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.Sockets try { var acceptSocket = await _listenSocket.AcceptAsync(); - acceptSocket.NoDelay = _options.NoDelay; + + // Only apply no delay to Tcp based endpoints + if (acceptSocket.LocalEndPoint is IPEndPoint) + { + acceptSocket.NoDelay = _options.NoDelay; + } var connection = new SocketConnection(acceptSocket, _memoryPool, _schedulers[_schedulerIndex], _trace, _options.MaxReadBufferSize, _options.MaxWriteBufferSize); diff --git a/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs b/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs new file mode 100644 index 0000000000..5992c7a232 --- /dev/null +++ b/src/Servers/Kestrel/test/FunctionalTests/UnixDomainSocketsTests.cs @@ -0,0 +1,117 @@ +// 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.Buffers; +using System.IO; +using System.Linq; +using System.Net.Sockets; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Connections; +using Microsoft.AspNetCore.Connections.Features; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Testing; +using Microsoft.AspNetCore.Testing.xunit; +using Xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + public class UnixDomainSocketsTest : TestApplicationErrorLoggerLoggedTest + { +#if LIBUV + [OSSkipCondition(OperatingSystems.Windows, SkipReason = "Libuv does not support unix domain sockets on Windows.")] +#else + [OSSkipCondition(OperatingSystems.Windows, WindowsVersions.Win7, WindowsVersions.Win8, WindowsVersions.Win81, WindowsVersions.Win2008R2, SkipReason = "UnixDomainSocketEndPoint is not supported on older versions of Windows")] +#endif + [ConditionalFact] + public async Task TestUnixDomainSocket() + { + var path = Path.GetTempFileName(); + + Delete(path); + + try + { + async Task EchoServer(ConnectionContext connection) + { + // For graceful shutdown + var notificationFeature = connection.Features.Get(); + + try + { + while (true) + { + var result = await connection.Transport.Input.ReadAsync(notificationFeature.ConnectionClosedRequested); + + if (result.IsCompleted) + { + break; + } + + await connection.Transport.Output.WriteAsync(result.Buffer.ToArray()); + + connection.Transport.Input.AdvanceTo(result.Buffer.End); + } + } + catch (OperationCanceledException) + { + + } + } + + var hostBuilder = TransportSelector.GetWebHostBuilder() + .UseKestrel(o => + { + o.ListenUnixSocket(path, builder => + { + builder.Run(EchoServer); + }); + }) + .ConfigureServices(AddTestLogging) + .Configure(c => { }); + + using (var host = hostBuilder.Build()) + { + await host.StartAsync(); + + using (var socket = new Socket(AddressFamily.Unix, SocketType.Stream, ProtocolType.Unspecified)) + { + await socket.ConnectAsync(new UnixDomainSocketEndPoint(path)); + + var data = Encoding.ASCII.GetBytes("Hello World"); + await socket.SendAsync(data, SocketFlags.None); + + var buffer = new byte[data.Length]; + var read = 0; + while (read < data.Length) + { + read += await socket.ReceiveAsync(buffer.AsMemory(read, buffer.Length - read), SocketFlags.None); + } + + Assert.Equal(data, buffer); + } + + await host.StopAsync(); + } + } + finally + { + Delete(path); + } + } + + private static void Delete(string path) + { + try + { + File.Delete(path); + } + catch (FileNotFoundException) + { + + } + } + } +}