Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Environment:
.NET Version: 9.0.201
OS: Windows 11 Pro for Workstations, Version 24H2, OS Build 26100.3624
Steps to Reproduce:
Use the provided code with HttpProtocols.Http1AndHttp2AndHttp3.
Run on port 50051/50052 (or any port).
Observe the "address already in use" error despite port checks passing.
Logs: Include the full error output from your last run.
Additional Info: Note that HTTP/1.1 and HTTP/2 work fine, and the issue is specific to QUIC/HTTP/3.
System.Net;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Security.Cryptography.X509Certificates;
using System.Threading;
using System.Threading.Tasks;
using Grpc.Core;
using DataService;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using GrpcServer;
using Microsoft.AspNetCore.Server.Kestrel.Core;
var builder = WebApplication.CreateBuilder(args);
// gRPC on port 50051
builder.Services.AddGrpc();
builder.Services.AddSingleton<GrpcServer.DataServiceImpl>();
builder.Services.AddSingleton<GrpcServer.WebSocketManager>();
builder.Services.AddLogging(logging => logging.AddConsole().SetMinimumLevel(LogLevel.Debug));
// Find an available port
int grpcPort = FindAvailablePort(50052);
Console.WriteLine($"Using port {grpcPort} for gRPC");
// Configure Kestrel with TLS
builder.WebHost.ConfigureKestrel(options =>
{
var serverCert = new X509Certificate2("./certs/server.pfx", "password");
options.ListenLocalhost(grpcPort, listenOptions =>
{
// Explicitly disable HTTP/3 for now
//listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
// To test HTTP/3, uncomment below and ensure port is truly free
listenOptions.Protocols = HttpProtocols.Http1AndHttp2AndHttp3;
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificate = serverCert;
httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsOptions.ClientCertificateValidation = (cert, chain, errors) =>
{
Console.WriteLine($"Validating client cert: {cert?.Subject}, Errors: {errors}");
return cert != null;
};
});
});
// WebSocket on port 8765
options.ListenLocalhost(8765, listenOptions =>
{
listenOptions.Protocols = HttpProtocols.Http1AndHttp2;
listenOptions.UseHttps(httpsOptions =>
{
httpsOptions.ServerCertificate = serverCert;
httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
httpsOptions.ClientCertificateValidation = (cert, chain, errors) =>
{
Console.WriteLine($"Validating client cert: {cert?.Subject}, Errors: {errors}");
return cert != null;
};
});
});
});
var app = builder.Build();
// Map gRPC services
app.MapGrpcService<GrpcServer.DataServiceImpl>();
app.MapGet("/", () => $"gRPC server is running on port {grpcPort} (HTTP/3 disabled)");
// WebSocket endpoint
app.UseWebSockets();
app.Use(async (context, next) =>
{
if (context.Connection.LocalPort == 8765 && context.Request.Path == "/")
{
if (context.WebSockets.IsWebSocketRequest)
{
var ws = await context.WebSockets.AcceptWebSocketAsync();
var wsManager = app.Services.GetRequiredService<GrpcServer.WebSocketManager>();
await wsManager.HandleConnection(ws, context);
}
else
{
context.Response.StatusCode = 400;
}
}
else
{
await next();
}
});
// Clean shutdown
CancellationTokenSource cts = new();
Console.CancelKeyPress += (s, e) =>
{
e.Cancel = true;
cts.Cancel();
};
try
{
await app.RunAsync(cts.Token);
}
finally
{
await app.StopAsync();
Console.WriteLine("Application stopped, ports should be released");
}
// Find an available port with retry logic
static int FindAvailablePort(int startPort)
{
for (int port = startPort; port < startPort + 100; port++)
{
for (int attempt = 0; attempt < 3; attempt++)
{
if (IsPortAvailable(port, true) && IsPortAvailable(port, false))
{
Console.WriteLine($"Port {port} confirmed available for TCP and UDP");
Thread.Sleep(500); // Extended delay to ensure stability
if (IsPortAvailable(port, true) && IsPortAvailable(port, false))
{
return port;
}
}
Console.WriteLine($"Port {port} retry {attempt + 1} failed");
Thread.Sleep(1000); // Longer wait before retry
}
}
throw new Exception("No available port found in range");
}
static bool IsPortAvailable(int port, bool checkTcp)
{
try
{
if (checkTcp)
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
}
else
{
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
socket.Bind(new IPEndPoint(IPAddress.Loopback, port));
}
return true;
}
catch (SocketException ex)
{
Console.WriteLine($"Port {port} unavailable for {(checkTcp ? "TCP" : "UDP")}: {ex.Message}");
return false;
}
}
Expected Behavior
If I disable HTTP/3, HttpProtocols.Http1AndHttp2, it works. if I use HttpProtocols.Http1AndHttp2AndHttp3, it caused port already used.
.\GrpcServer.exe
Port 50051 confirmed available for TCP and UDP
Using port 50051 for gRPC
dbug: Microsoft.Extensions.Hosting.Internal.Host[1]
Hosting starting
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[7]
Middleware configuration started with options: {AllowedHosts = *, AllowEmptyHosts = True, IncludeFailureMessage = True}
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]
Wildcard detected, all requests with hosts will be allowed.
info: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[9]
Certificate with thumbprint 0EA50776CB4E269B2A6989FAFE5DDCD299AD8965 lacks the subjectAlternativeName (SAN) extension and may not be accepted by browsers.
info: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[9]
Certificate with thumbprint 0EA50776CB4E269B2A6989FAFE5DDCD299AD8965 lacks the subjectAlternativeName (SAN) extension and may not be accepted by browsers.
info: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[9]
Certificate with thumbprint 0EA50776CB4E269B2A6989FAFE5DDCD299AD8965 lacks the subjectAlternativeName (SAN) extension and may not be accepted by browsers.
info: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[9]
Certificate with thumbprint 0EA50776CB4E269B2A6989FAFE5DDCD299AD8965 lacks the subjectAlternativeName (SAN) extension and may not be accepted by browsers.
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:50051
info: Microsoft.Hosting.Lifetime[14]
Now listening on: https://localhost:8765
dbug: Microsoft.AspNetCore.Hosting.Diagnostics[13]
Loaded hosting startup assembly GrpcServer
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
Hosting environment: Production
info: Microsoft.Hosting.Lifetime[0]
Content root path: D:\test\workspace_lean_code_snip\http2_2_websocket_Grok_worked\C#\GrpcServer\bin\Debug\net9.0
dbug: Microsoft.Extensions.Hosting.Internal.Host[2]
Hosting started
info: Microsoft.Hosting.Lifetime[0]
Application is shutting down...
dbug: Microsoft.Extensions.Hosting.Internal.Host[3]
Hosting stopping
dbug: Microsoft.Extensions.Hosting.Internal.Host[4]
Hosting stopped
dbug: Microsoft.Extensions.Hosting.Internal.Host[3]
Hosting stopping
dbug: Microsoft.Extensions.Hosting.Internal.Host[4]
Hosting stopped
Application stopped, ports should be released
Steps To Reproduce
Steps to Reproduce:
Use the provided code with HttpProtocols.Http1AndHttp2AndHttp3.
Run on port 50052 (or any port).
Observe the "address already in use" error despite port checks passing.
Exceptions (if any)
No response
.NET Version
9.0.201
Anything else?
.\GrpcServer.exe
Port 50052 confirmed available for TCP and UDP
Using port 50052 for gRPC
dbug: Microsoft.Extensions.Hosting.Internal.Host[1]
Hosting starting
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[7]
Middleware configuration started with options: {AllowedHosts = *, AllowEmptyHosts = True, IncludeFailureMessage = True}
dbug: Microsoft.AspNetCore.HostFiltering.HostFilteringMiddleware[0]
Wildcard detected, all requests with hosts will be allowed.
info: Microsoft.AspNetCore.Server.Kestrel.Https.Internal.HttpsConnectionMiddleware[9]
Certificate with thumbprint 0EA50776CB4E269B2A6989FAFE5DDCD299AD8965 lacks the subjectAlternativeName (SAN) extension and may not be accepted by browsers.
dbug: Microsoft.AspNetCore.Server.Kestrel.Transport.Quic[20]
QUIC listener starting with configured endpoint 127.0.0.1:50052.
fail: Microsoft.Extensions.Hosting.Internal.Host[11]
Hosting failed to start
System.IO.IOException: Failed to bind to address https://127.0.0.1:50052: address already in use.
---> Microsoft.AspNetCore.Connections.AddressInUseException: Only one usage of each socket address (protocol/network address/port) is normally permitted.
---> System.Net.Sockets.SocketException (80072740, 10048): Only one usage of each socket address (protocol/network address/port) is normally permitted.
at System.Net.Quic.ThrowHelper.ThrowMsQuicException(Int32 status, String message)
at System.Net.Quic.QuicListener..ctor(QuicListenerOptions options)
at System.Net.Quic.QuicListener.ListenAsync(QuicListenerOptions options, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicConnectionListener.CreateListenerAsync()
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicConnectionListener.CreateListenerAsync()
at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportFactory.BindAsync(EndPoint endpoint, IFeatureCollection features, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_01.<<StartAsync>g__OnBind|0>d.MoveNext() --- End of stack trace from previous location --- at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) --- End of inner exception stack trace --- at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication
1 application, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.Internal.Host.b__14_1(IHostedService service, CancellationToken token)
at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List
1 exceptions, Func3 operation) dbug: Microsoft.Extensions.Hosting.Internal.Host[3] Hosting stopping dbug: Microsoft.Extensions.Hosting.Internal.Host[4] Hosting stopped Application stopped, ports should be released Unhandled exception. System.IO.IOException: Failed to bind to address https://127.0.0.1:50052: address already in use. ---> Microsoft.AspNetCore.Connections.AddressInUseException: Only one usage of each socket address (protocol/network address/port) is normally permitted. ---> System.Net.Sockets.SocketException (80072740, 10048): Only one usage of each socket address (protocol/network address/port) is normally permitted. at System.Net.Quic.ThrowHelper.ThrowMsQuicException(Int32 status, String message) at System.Net.Quic.QuicListener..ctor(QuicListenerOptions options) at System.Net.Quic.QuicListener.ListenAsync(QuicListenerOptions options, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicConnectionListener.CreateListenerAsync() --- End of inner exception stack trace --- at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.Internal.QuicConnectionListener.CreateListenerAsync() at Microsoft.AspNetCore.Server.Kestrel.Transport.Quic.QuicTransportFactory.BindAsync(EndPoint endpoint, IFeatureCollection features, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.TransportManager.BindAsync(EndPoint endPoint, MultiplexedConnectionDelegate multiplexedConnectionDelegate, ListenOptions listenOptions, CancellationToken cancellationToken) at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.<>c__DisplayClass28_0
1.<g__OnBind|0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.BindEndpointAsync(ListenOptions endpoint, AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.LocalhostListenOptions.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.Internal.AddressBinder.EndpointsStrategy.BindAsync(AddressBindContext context, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.BindAsync(CancellationToken cancellationToken)
at Microsoft.AspNetCore.Server.Kestrel.Core.KestrelServerImpl.StartAsync[TContext](IHttpApplication1 application, CancellationToken cancellationToken) at Microsoft.AspNetCore.Hosting.GenericWebHostService.StartAsync(CancellationToken cancellationToken) at Microsoft.Extensions.Hosting.Internal.Host.<StartAsync>b__14_1(IHostedService service, CancellationToken token) at Microsoft.Extensions.Hosting.Internal.Host.ForeachService[T](IEnumerable
1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List1 exceptions, Func
3 operation)
at Microsoft.Extensions.Hosting.Internal.Host.StartAsync(CancellationToken cancellationToken)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Microsoft.Extensions.Hosting.HostingAbstractionsHostExtensions.RunAsync(IHost host, CancellationToken token)
at Program.
at Program.$(String[] args) in D:\test\workspace_lean_code_snip\http2_2_websocket_Grok_worked\C#\GrpcServer\Program.cs:line 119
at Program.(String[] args)