Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[H/3] Fix code passed into QuicConnection.CloseAsync and QuicStream.Abort #55282

Merged
merged 11 commits into from
May 3, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,11 @@ public override void Abort(ConnectionAbortedException abortReason)
}

var resolvedErrorCode = _error ?? 0;
// Allowed error codes are up to 62 bits non-negative integer values: https://www.rfc-editor.org/rfc/rfc9000.html#integer-encoding
if (resolvedErrorCode < 0 || resolvedErrorCode > ((1L << 62) - 1))
{
resolvedErrorCode = _context.Options.DefaultCloseErrorCode;
amcasey marked this conversation as resolved.
Show resolved Hide resolved
}
_abortReason = ExceptionDispatchInfo.Capture(abortReason);
QuicLog.ConnectionAbort(_log, this, resolvedErrorCode, abortReason.Message);
_closeTask = _connection.CloseAsync(errorCode: resolvedErrorCode).AsTask();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -502,6 +502,11 @@ public override void Abort(ConnectionAbortedException abortReason)
}

var resolvedErrorCode = _error ?? 0;
// Allowed error codes are up to 62 bits non-negative integer values: https://www.rfc-editor.org/rfc/rfc9000.html#integer-encoding
if (resolvedErrorCode < 0 || resolvedErrorCode > ((1L << 62) - 1))
{
resolvedErrorCode = _context.Options.DefaultStreamErrorCode;
amcasey marked this conversation as resolved.
Show resolved Hide resolved
}
QuicLog.StreamAbort(_log, this, resolvedErrorCode, abortReason.Message);

if (stream.CanRead)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,16 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using Microsoft.AspNetCore.InternalTesting;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Quic;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Diagnostics.Metrics;
using Microsoft.Extensions.Diagnostics.Metrics.Testing;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Primitives;
using Xunit;

namespace Interop.FunctionalTests.Http3;

Expand Down Expand Up @@ -1606,6 +1605,90 @@ public async Task GET_ClientDisconnected_ConnectionAbortRaised()
}
}

[ConditionalFact]
[MsQuicSupported]
public async Task GET_ClientIdlesOut_ConnectionAbortRaised()
amcasey marked this conversation as resolved.
Show resolved Hide resolved
{
// Arrange
var connectionClosedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var connectionStartedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var builder = CreateHostBuilder(
context =>
{
return Task.CompletedTask;
},
configureKestrel: kestrel =>
{
kestrel.Listen(IPAddress.Parse("127.0.0.1"), 0, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http3;
listenOptions.UseHttps(TestResources.GetTestCertificate());

IMultiplexedConnectionBuilder multiplexedConnectionBuilder = listenOptions;
multiplexedConnectionBuilder.Use(next =>
{
return context =>
{
connectionStartedTcs.SetResult();
context.ConnectionClosed.Register(() => connectionClosedTcs.SetResult());
return next(context);
};
});
});
});

// Error codes of this form are reserved for unknown errors
const int closeErrorCode = 0xBC; // 0x1f * 5 + 0x21

// Set a non-standard error code so we can be sure the option was consumed
builder.ConfigureServices(services =>
{
services.Configure<QuicTransportOptions>(options =>
{
options.DefaultCloseErrorCode = closeErrorCode;
});
});

var logTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);

TestSink.MessageLogged += context =>
{
if (context.LoggerName == "Microsoft.AspNetCore.Server.Kestrel.Transport.Quic" &&
context.EventId.Id == 6) // application aborted connection
{
Assert.Contains($"aborted by application with error code {closeErrorCode}", context.Message);
logTcs.SetResult();
}
};

using (var host = builder.Build())
{
await host.StartAsync();

var client = HttpHelpers.CreateClient(idleTimeout: TimeSpan.FromSeconds(1));
amcasey marked this conversation as resolved.
Show resolved Hide resolved
var port = host.GetPort();

// Act
var request1 = new HttpRequestMessage(HttpMethod.Get, $"https://127.0.0.1:{port}/");
request1.Version = HttpVersion.Version30;
request1.VersionPolicy = HttpVersionPolicy.RequestVersionExact;

var response1 = await client.SendAsync(request1, CancellationToken.None);
response1.EnsureSuccessStatusCode();

await connectionStartedTcs.Task.DefaultTimeout();
// Do not dispose the client, wait for it to idle out.

Logger.LogInformation("Waiting for server to receive connection close.");
await connectionClosedTcs.Task.DefaultTimeout();

// Server has aborted connection.
await logTcs.Task.DefaultTimeout();

await host.StopAsync();
}
}

[ConditionalFact]
[MsQuicSupported]
public async Task ConnectionLifetimeNotificationFeature_RequestClose_ConnectionEnds()
Expand Down