Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/aspnetcore.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJames Newton-King <james@newtonking.com>2022-10-11 06:27:08 +0300
committerJames Newton-King <james@newtonking.com>2022-10-13 11:45:44 +0300
commitbab03743bfb88d0f3df61550033b30ace1a41a8b (patch)
tree768037a6c5c54e152fe4846ecd537398a436264a
parentb9aa773c67757a0c6abd7841bb9c859bad979481 (diff)
-rw-r--r--eng/Versions.props2
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeConnectionListener.cs109
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/src/Internal/NamedPipeTransportFactory.cs23
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeEndPoint.cs28
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/src/NamedPipeTransportOptions.cs12
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/src/PublicAPI.Unshipped.txt8
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeConnectionListenerTests.cs110
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/test/NamedPipeTestHelpers.cs13
-rw-r--r--src/Servers/Kestrel/Transport.NamedPipes/test/WebHostTests.cs9
m---------src/submodules/MessagePack-CSharp0
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