diff options
author | James Newton-King <james@newtonking.com> | 2022-10-11 06:27:08 +0300 |
---|---|---|
committer | James Newton-King <james@newtonking.com> | 2022-10-13 11:45:44 +0300 |
commit | bab03743bfb88d0f3df61550033b30ace1a41a8b (patch) | |
tree | 768037a6c5c54e152fe4846ecd537398a436264a | |
parent | b9aa773c67757a0c6abd7841bb9c859bad979481 (diff) |
10 files changed, 242 insertions, 72 deletions
diff --git a/eng/Versions.props b/eng/Versions.props index c724cab1cd..7960cb9c56 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -8,7 +8,7 @@ <PropertyGroup Label="Version settings"> <AspNetCoreMajorVersion>6</AspNetCoreMajorVersion> <AspNetCoreMinorVersion>0</AspNetCoreMinorVersion> - <AspNetCorePatchVersion>7</AspNetCorePatchVersion> + <AspNetCorePatchVersion>6</AspNetCorePatchVersion> <ValidateBaseline>false</ValidateBaseline> <!-- When StabilizePackageVersion is set to 'true', this branch will produce stable outputs for 'Shipping' packages diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs index 3532281779..89749b2926 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs @@ -2,12 +2,14 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Buffers; +using System.Diagnostics; using System.IO.Pipelines; using System.IO.Pipes; using System.Net; using System.Threading.Channels; using Microsoft.AspNetCore.Connections; using Microsoft.Extensions.Logging; +using NamedPipeOptions = System.IO.Pipes.PipeOptions; using PipeOptions = System.IO.Pipelines.PipeOptions; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal; @@ -20,21 +22,24 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener private readonly CancellationTokenSource _listeningTokenSource = new CancellationTokenSource(); private readonly CancellationToken _listeningToken; private readonly Channel<ConnectionContext> _acceptedQueue; - private readonly Task _listeningTask; private readonly MemoryPool<byte> _memoryPool; private readonly PipeOptions _inputOptions; private readonly PipeOptions _outputOptions; + private readonly Mutex _mutex; + private Task? _listeningTask; private int _disposed; public NamedPipeConnectionListener( NamedPipeEndPoint endpoint, NamedPipeTransportOptions options, - ILoggerFactory loggerFactory) + ILoggerFactory loggerFactory, + Mutex mutex) { _log = loggerFactory.CreateLogger("Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes"); _endpoint = endpoint; _options = options; - _acceptedQueue = Channel.CreateBounded<ConnectionContext>(new BoundedChannelOptions(options.Backlog)); + _mutex = mutex; + _acceptedQueue = Channel.CreateBounded<ConnectionContext>(new BoundedChannelOptions(options.Backlog) { SingleWriter = true }); _memoryPool = options.MemoryPoolFactory(); _listeningToken = _listeningTokenSource.Token; @@ -43,36 +48,47 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener _inputOptions = new PipeOptions(_memoryPool, PipeScheduler.ThreadPool, PipeScheduler.Inline, maxReadBufferSize, maxReadBufferSize / 2, useSynchronizationContext: false); _outputOptions = new PipeOptions(_memoryPool, PipeScheduler.Inline, PipeScheduler.ThreadPool, maxWriteBufferSize, maxWriteBufferSize / 2, useSynchronizationContext: false); + } + + public void Start() + { + Debug.Assert(_listeningTask == null, "Already started"); - // Start after all fields are initialized. - _listeningTask = StartAsync(); + // Start first stream inline to catch creation errors. + var initialStream = CreateServerStream(); + + _listeningTask = StartAsync(initialStream); } public EndPoint EndPoint => _endpoint; - private async Task StartAsync() + private async Task StartAsync(NamedPipeServerStream nextStream) { try { while (true) { - NamedPipeServerStream stream; - try { - _listeningToken.ThrowIfCancellationRequested(); - - stream = NamedPipeServerStreamAcl.Create( - _endpoint.PipeName, - PipeDirection.InOut, - NamedPipeServerStream.MaxAllowedServerInstances, - PipeTransmissionMode.Byte, - _endpoint.PipeOptions, - inBufferSize: 0, // Buffer in System.IO.Pipelines - outBufferSize: 0, // Buffer in System.IO.Pipelines - _options.PipeSecurity); + var stream = nextStream; await stream.WaitForConnectionAsync(_listeningToken); + + var connection = new NamedPipeConnection(stream, _endpoint, _log, _memoryPool, _inputOptions, _outputOptions); + connection.Start(); + + // Create the next stream before writing connected stream to the channel. + // This ensures there is always a created stream and another process can't + // create a stream with the same name with different a access policy. + nextStream = CreateServerStream(); + + while (!_acceptedQueue.Writer.TryWrite(connection)) + { + if (!await _acceptedQueue.Writer.WaitToWriteAsync(_listeningToken)) + { + throw new InvalidOperationException("Accept queue writer was unexpectedly closed."); + } + } } catch (OperationCanceledException ex) when (_listeningToken.IsCancellationRequested) { @@ -80,13 +96,9 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener NamedPipeLog.ConnectionListenerAborted(_log, ex); break; } - - var connection = new NamedPipeConnection(stream, _endpoint, _log, _memoryPool, _inputOptions, _outputOptions); - connection.Start(); - - _acceptedQueue.Writer.TryWrite(connection); } + nextStream.Dispose(); _acceptedQueue.Writer.TryComplete(); } catch (Exception ex) @@ -95,6 +107,41 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener } } + private NamedPipeServerStream CreateServerStream() + { + NamedPipeServerStream stream; + var pipeOptions = NamedPipeOptions.Asynchronous | NamedPipeOptions.WriteThrough; + if (_options.CurrentUserOnly) + { + pipeOptions |= NamedPipeOptions.CurrentUserOnly; + } + + if (_options.PipeSecurity != null) + { + stream = NamedPipeServerStreamAcl.Create( + _endpoint.PipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + pipeOptions, + inBufferSize: 0, // Buffer in System.IO.Pipelines + outBufferSize: 0, // Buffer in System.IO.Pipelines + _options.PipeSecurity); + } + else + { + stream = new NamedPipeServerStream( + _endpoint.PipeName, + PipeDirection.InOut, + NamedPipeServerStream.MaxAllowedServerInstances, + PipeTransmissionMode.Byte, + pipeOptions, + inBufferSize: 0, + outBufferSize: 0); + } + return stream; + } + public async ValueTask<ConnectionContext?> AcceptAsync(CancellationToken cancellationToken = default) { while (await _acceptedQueue.Reader.WaitToReadAsync(cancellationToken)) @@ -109,6 +156,8 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener return null; } + public ValueTask UnbindAsync(CancellationToken cancellationToken = default) => DisposeAsync(); + public async ValueTask DisposeAsync() { // A stream may be waiting on WaitForConnectionAsync when dispose happens. @@ -119,12 +168,10 @@ internal sealed class NamedPipeConnectionListener : IConnectionListener } _listeningTokenSource.Dispose(); - await _listeningTask; - } - - public async ValueTask UnbindAsync(CancellationToken cancellationToken = default) - { - _listeningTokenSource.Cancel(); - await _listeningTask; + _mutex.Dispose(); + if (_listeningTask != null) + { + await _listeningTask; + } } } diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeTransportFactory.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeTransportFactory.cs index e87519a6fd..aadbe963fb 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeTransportFactory.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeTransportFactory.cs @@ -25,12 +25,31 @@ public sealed class NamedPipeTransportFactory : IConnectionListenerFactory public ValueTask<IConnectionListener> BindAsync(EndPoint endpoint, CancellationToken cancellationToken = default) { - if (endpoint is not NamedPipeEndPoint np) + ArgumentNullException.ThrowIfNull(endpoint); + + if (endpoint is not NamedPipeEndPoint namedPipeEndPoint) { throw new NotSupportedException($"{endpoint.GetType()} is not supported."); } + if (namedPipeEndPoint.ServerName != NamedPipeEndPoint.LocalComputerServerName) + { + throw new NotSupportedException($@"Server name '{namedPipeEndPoint.ServerName}' is invalid. The server name must be "".""."); + } + + // Creating a named pipe server with an name isn't exclusive. Create a mutex with the pipe name to prevent multiple endpoints + // accidently sharing the same pipe name. Will detect across Kestrel processes. + // Note that this doesn't prevent other applications from using the pipe name. + var mutexName = "Kestrel-NamedPipe-" + namedPipeEndPoint.PipeName; + var mutex = new Mutex(false, mutexName, out var createdNew); + if (!createdNew) + { + mutex.Dispose(); + throw new AddressInUseException($"Named pipe '{namedPipeEndPoint.PipeName}' is already in use by Kestrel."); + } - var listener = new NamedPipeConnectionListener(np, _options, _loggerFactory); + var listener = new NamedPipeConnectionListener(namedPipeEndPoint, _options, _loggerFactory, mutex); + listener.Start(); + return new ValueTask<IConnectionListener>(listener); } } diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeEndPoint.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeEndPoint.cs index fe530eae41..a5e7997407 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeEndPoint.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeEndPoint.cs @@ -1,7 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO.Pipes; +using System.Diagnostics.CodeAnalysis; using System.Net; using System.Net.Sockets; @@ -12,37 +12,45 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes; /// </summary> public sealed class NamedPipeEndPoint : IPEndPoint { + internal const string LocalComputerServerName = "."; + /// <summary> /// Initializes a new instance of the <see cref="NamedPipeEndPoint"/> class. /// </summary> /// <param name="pipeName">The name of the pipe.</param> /// <param name="serverName">The name of the remote computer to connect to, or "." to specify the local computer.</param> - /// <param name="pipeOptions">One of the enumeration values that determines how to open or create the pipe.</param> - public NamedPipeEndPoint(string pipeName, string serverName = ".", PipeOptions pipeOptions = PipeOptions.Asynchronous) : base(IPAddress.Any, 80) + public NamedPipeEndPoint(string pipeName, string serverName = LocalComputerServerName) : base(IPAddress.Any, 80) { ServerName = serverName; PipeName = pipeName; - PipeOptions = pipeOptions; } /// <summary> - /// Gets the name of the remote computer to connect to. + /// Gets the name of the remote computer. The server name must be ".", the local computer, when creating a server. /// </summary> public string ServerName { get; } /// <summary> /// Gets the name of the pipe. /// </summary> public string PipeName { get; } - /// <summary> - /// Gets the pipe options. - /// </summary> - public PipeOptions PipeOptions { get; set; } /// <summary> /// Gets the pipe name represented by this <see cref="NamedPipeEndPoint"/> instance. /// </summary> public override string ToString() { - return PipeName; + return $"pipe:{ServerName}/{PipeName}"; + } + + /// <inheritdoc/> + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is NamedPipeEndPoint other && other.ServerName == ServerName && other.PipeName == PipeName; + } + + /// <inheritdoc/> + public override int GetHashCode() + { + return ServerName.GetHashCode() ^ PipeName.GetHashCode(); } } diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs index 073cabcc6a..ba1d8b761c 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs @@ -44,6 +44,18 @@ public sealed class NamedPipeTransportOptions public long? MaxWriteBufferSize { get; set; } = 64 * 1024; /// <summary> + /// Gets or sets a value that indicates that the pipe can only be connected to by a client created by + /// the same user account. + /// <para> + /// On Windows, a value of true verifies both the user account and elevation level. + /// </para> + /// </summary> + /// <remarks> + /// Defaults to true. + /// </remarks> + public bool CurrentUserOnly { get; set; } = true; + + /// <summary> /// Gets or sets the security information that determines the access control and audit security for pipes. /// </summary> public PipeSecurity? PipeSecurity { get; set; } diff --git a/src/Servers/Kestrel/Transport.NamedPipes/src/PublicAPI.Unshipped.txt b/src/Servers/Kestrel/Transport.NamedPipes/src/PublicAPI.Unshipped.txt index a677a39671..39590e5704 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/src/PublicAPI.Unshipped.txt +++ b/src/Servers/Kestrel/Transport.NamedPipes/src/PublicAPI.Unshipped.txt @@ -4,14 +4,14 @@ Microsoft.AspNetCore.Connections.Features.IConnectionNamedPipeFeature.NamedPipe. Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal.NamedPipeTransportFactory Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal.NamedPipeTransportFactory.BindAsync(System.Net.EndPoint! endpoint, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask<Microsoft.AspNetCore.Connections.IConnectionListener!> Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint -Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.NamedPipeEndPoint(string! pipeName, string! serverName = ".", System.IO.Pipes.PipeOptions pipeOptions = System.IO.Pipes.PipeOptions.Asynchronous) -> void +Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.NamedPipeEndPoint(string! pipeName, string! serverName = ".") -> void Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.PipeName.get -> string! -Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.PipeOptions.get -> System.IO.Pipes.PipeOptions -Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.PipeOptions.set -> void Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.ServerName.get -> string! Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.Backlog.get -> int Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.Backlog.set -> void +Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.CurrentUserOnly.get -> bool +Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.CurrentUserOnly.set -> void Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.MaxReadBufferSize.get -> long? Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.MaxReadBufferSize.set -> void Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.MaxWriteBufferSize.get -> long? @@ -19,5 +19,7 @@ Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptio Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.NamedPipeTransportOptions() -> void Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.PipeSecurity.get -> System.IO.Pipes.PipeSecurity? Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions.PipeSecurity.set -> void +override Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.Equals(object? obj) -> bool +override Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.GetHashCode() -> int override Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeEndPoint.ToString() -> string! ~Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal.NamedPipeTransportFactory.NamedPipeTransportFactory(Microsoft.Extensions.Logging.ILoggerFactory! loggerFactory, Microsoft.Extensions.Options.IOptions<Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.NamedPipeTransportOptions!>! options) -> void diff --git a/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeConnectionListenerTests.cs b/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeConnectionListenerTests.cs index c83655b28d..b845ab25e7 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeConnectionListenerTests.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeConnectionListenerTests.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO.Pipes; +using Microsoft.AspNetCore.Connections; using Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Internal; using Microsoft.AspNetCore.Testing; +using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Server.Kestrel.Transport.NamedPipes.Tests; @@ -27,19 +30,25 @@ public class NamedPipeConnectionListenerTests : TestApplicationErrorLoggerLogged // Arrange await using var connectionListener = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory); - // Act - var acceptTask = connectionListener.AcceptAsync(); - - await using var clientStream = NamedPipeTestHelpers.CreateClientStream(connectionListener.EndPoint); - await clientStream.ConnectAsync(); - - // Assert - var serverConnection = await acceptTask.DefaultTimeout(); - Assert.False(serverConnection.ConnectionClosed.IsCancellationRequested); - - await serverConnection.DisposeAsync().AsTask().DefaultTimeout(); - - Assert.True(serverConnection.ConnectionClosed.IsCancellationRequested); + // Stream 1 + var acceptTask1 = connectionListener.AcceptAsync(); + await using var clientStream1 = NamedPipeTestHelpers.CreateClientStream(connectionListener.EndPoint); + await clientStream1.ConnectAsync(); + + var serverConnection1 = await acceptTask1.DefaultTimeout(); + Assert.False(serverConnection1.ConnectionClosed.IsCancellationRequested, "Connection 1 should be open"); + await serverConnection1.DisposeAsync().AsTask().DefaultTimeout(); + Assert.True(serverConnection1.ConnectionClosed.IsCancellationRequested, "Connection 1 should be closed"); + + // Stream 2 + var acceptTask2 = connectionListener.AcceptAsync(); + await using var clientStream2 = NamedPipeTestHelpers.CreateClientStream(connectionListener.EndPoint); + await clientStream2.ConnectAsync(); + + var serverConnection2 = await acceptTask2.DefaultTimeout(); + Assert.False(serverConnection2.ConnectionClosed.IsCancellationRequested, "Connection 2 should be open"); + await serverConnection2.DisposeAsync().AsTask().DefaultTimeout(); + Assert.True(serverConnection2.ConnectionClosed.IsCancellationRequested, "Connection 2 should be closed"); } [Fact] @@ -60,6 +69,61 @@ public class NamedPipeConnectionListenerTests : TestApplicationErrorLoggerLogged } [Fact] + public async Task AcceptAsync_HitBacklogLimit_ClientConnectionsSuccessfullyAccepted() + { + // Arrange + var options = new NamedPipeTransportOptions { Backlog = 2 }; + await using var connectionListener = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory, options: options); + + // Act + var clients = new List<ClientStreamContext>(); + var connectBlocked = false; + for (var i = 0; i < 100; i++) + { + Logger.LogInformation($"Connecting client {i}."); + + var clientStream = NamedPipeTestHelpers.CreateClientStream(connectionListener.EndPoint); + var connectTask = clientStream.ConnectAsync(); + + clients.Add(new ClientStreamContext(clientStream, connectTask)); + + try + { + // Attempt to connect for half a second. Assume we've hit the limit if this value is exceeded. + await connectTask.WaitAsync(TimeSpan.FromSeconds(0.5)); + Logger.LogInformation($"Client {i} connect success."); + } + catch (TimeoutException) + { + Logger.LogInformation($"Client {i} connect timeout."); + connectBlocked = true; + break; + } + } + + Assert.True(connectBlocked, "Connect should be blocked before reaching the end of the connect loop."); + + for (var i = 0; i < clients.Count; i++) + { + var client = clients[i]; + Logger.LogInformation($"Accepting client {i} on the server."); + var serverConnectionTask = connectionListener.AcceptAsync(); + + await client.ConnectTask.DefaultTimeout(); + client.ServerConnection = await serverConnectionTask.DefaultTimeout(); + + Logger.LogInformation($"Asserting client {i} is connected to the server."); + Assert.True(client.ClientStream.IsConnected, "IsConnected should be true."); + Assert.True(client.ConnectTask.IsCompletedSuccessfully, "ConnectTask should be completed."); + } + } + + private record ClientStreamContext(NamedPipeClientStream ClientStream, Task ConnectTask) + { + public ConnectionContext ServerConnection { get; set; } + } + + [Fact] public async Task AcceptAsync_DisposeAfterCall_CleanExitAndLog() { // Arrange @@ -76,15 +140,27 @@ public class NamedPipeConnectionListenerTests : TestApplicationErrorLoggerLogged Assert.Contains(LogMessages, m => m.EventId.Name == "ConnectionListenerAborted"); } - [Fact(Skip = "No warning when dupliate pipe name used with server?")] + [Fact] public async Task BindAsync_ListenersSharePort_ThrowAddressInUse() { // Arrange - await using var connectionListener = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory); + await using var connectionListener1 = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory); + var pipeName = ((NamedPipeEndPoint)connectionListener1.EndPoint).PipeName; // Act & Assert - var pipeName = ((NamedPipeEndPoint)connectionListener.EndPoint).PipeName; + await Assert.ThrowsAsync<AddressInUseException>(() => NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory, pipeName: pipeName)); + } + + [Fact] + public async Task BindAsync_ListenersSharePort_DisposeFirstListener_Success() + { + // Arrange + var connectionListener1 = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory); + var pipeName = ((NamedPipeEndPoint)connectionListener1.EndPoint).PipeName; + await connectionListener1.DisposeAsync(); - await Assert.ThrowsAsync<Exception>(() => NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory, pipeName: pipeName)); + // Act & Assert + await using var connectionListener2 = await NamedPipeTestHelpers.CreateConnectionListenerFactory(LoggerFactory, pipeName: pipeName); + Assert.Equal(connectionListener1.EndPoint, connectionListener2.EndPoint); } } diff --git a/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeTestHelpers.cs b/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeTestHelpers.cs index 9cdac00fcd..eda7dd5e83 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeTestHelpers.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeTestHelpers.cs @@ -20,21 +20,24 @@ internal static class NamedPipeTestHelpers public static string GetUniquePipeName() => "Kestrel-" + Path.GetRandomFileName(); public static NamedPipeTransportFactory CreateTransportFactory( - ILoggerFactory loggerFactory = null) + ILoggerFactory loggerFactory = null, + NamedPipeTransportOptions options = null) { - var options = new NamedPipeTransportOptions(); + options ??= new NamedPipeTransportOptions(); return new NamedPipeTransportFactory(loggerFactory ?? NullLoggerFactory.Instance, Options.Create(options)); } public static async Task<NamedPipeConnectionListener> CreateConnectionListenerFactory( ILoggerFactory loggerFactory = null, - string pipeName = null) + string pipeName = null, + NamedPipeTransportOptions options = null) { - var transportFactory = CreateTransportFactory(loggerFactory); + var transportFactory = CreateTransportFactory(loggerFactory, options); var endpoint = new NamedPipeEndPoint(pipeName ?? GetUniquePipeName()); - return (NamedPipeConnectionListener)await transportFactory.BindAsync(endpoint, cancellationToken: CancellationToken.None); + var listener = (NamedPipeConnectionListener)await transportFactory.BindAsync(endpoint, cancellationToken: CancellationToken.None); + return listener; } public static NamedPipeClientStream CreateClientStream(EndPoint remoteEndPoint, TokenImpersonationLevel? impersonationLevel = null) diff --git a/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs b/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs index 48fbd00900..fc27caa35b 100644 --- a/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs +++ b/src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs @@ -74,7 +74,8 @@ public class WebHostTests : LoggedTest } } - [Fact] + [ConditionalFact] + [OSSkipCondition(OperatingSystems.Linux | OperatingSystems.MacOSX, SkipReason = "Impersonation is only supported on Windows.")] public async Task ListenNamedPipeEndpoint_Impersonation_ClientSuccess() { AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal); @@ -92,6 +93,7 @@ public class WebHostTests : LoggedTest ps.AddAccessRule(new PipeAccessRule("Users", PipeAccessRights.ReadWrite | PipeAccessRights.CreateNewInstance, AccessControlType.Allow)); options.PipeSecurity = ps; + options.CurrentUserOnly = false; }); webHostBuilder .UseKestrel(o => @@ -221,7 +223,8 @@ public class WebHostTests : LoggedTest }; } - [Theory] + [ConditionalTheory] + [OSSkipCondition(OperatingSystems.MacOSX, SkipReason = "Missing SslStream ALPN support: https://github.com/dotnet/runtime/issues/27727")] [InlineData(HttpProtocols.Http1)] [InlineData(HttpProtocols.Http2)] public async Task ListenNamedPipeEndpoint_Tls_ClientSuccess(HttpProtocols protocols) @@ -266,7 +269,7 @@ public class WebHostTests : LoggedTest }; // Act - var response = await client.SendAsync(request); + var response = await client.SendAsync(request).DefaultTimeout(); // Assert response.EnsureSuccessStatusCode(); diff --git a/src/submodules/MessagePack-CSharp b/src/submodules/MessagePack-CSharp -Subproject 6caf2996c82d2b91528fad41e9c78e09770e73d +Subproject fe9fa0834d18492eb229ff2923024af2c87553f |