dotnet / dotnet/aspnetcore

HTTP/3 QUIC Binding Fails with 'Address Already in Use' Despite Port Being Free in .NET 9.0.201

Open
#61,403 0 comments 0 reactions 0 assignees View on GitHub
area-networking HTTP3
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [x] 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();
builder.Services.AddSingleton();
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();
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();
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_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](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](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 exceptions, Func`3 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](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](IEnumerable`1 services, CancellationToken token, Boolean concurrent, Boolean abortOnFirstException, List`1 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.$(String[] args) in D:\test\workspace_lean\_code_snip\http2_2_websocket_Grok_worked\C#\GrpcServer\Program.cs:line 113
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)

Contributor guide

Open the contributing guide

Research direction

Start with the provided ConfigureKestrel and ListenLocalhost setup and the QuicListener stack trace; reproduce on Windows with HttpProtocols.Http1AndHttp2AndHttp3, then compare it with HTTP/1.1 and HTTP/2. Done means the QUIC bind failure is explained and a verified fix or precise limitation is documented; no repository file or test is named in the issue.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp
Domain
backend, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.