-
Notifications
You must be signed in to change notification settings - Fork 5.3k
Fix HTTP3 stress #69182
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
Fix HTTP3 stress #69182
Conversation
|
Tagging subscribers to this area: @dotnet/ncl Issue DetailsContributes to #67442. This PR builds latest MsQuic inside the http stress docker image. It also adds a (temporary) workaround for microsoft/msquic#2704 to unblock running H3 stress.
|
|
cc: @ManickaP |
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
Outdated
Show resolved
Hide resolved
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
Outdated
Show resolved
Hide resolved
|
/azp run runtime-libraries stress-http |
|
Azure Pipelines successfully started running 1 pipeline(s). |
src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile
Outdated
Show resolved
Hide resolved
a34a91e to
44ca4ad
Compare
|
/azp run runtime-libraries stress-http |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
The stress CI leg is failing because we run out of space when building the docker image, I filed dotnet/arcade#9349 |
|
/azp run runtime-libraries stress-http |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
/azp run runtime-libraries stress-http |
|
Azure Pipelines successfully started running 1 pipeline(s). |
ManickaP
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM in general.
Some more comments that are not necessarily prereqs for this PR:
- what if you use IPv4.Any and client tries IPv6 address? How do sockets behave?
- I ran this test:
https://github.com/dotnet/runtime/blob/main/src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs#L31
But I changedIPv6Loopback->IPv6Anyand it got stuck somewhere in the middle of a handshake. I didn't investigate further.
| return MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS); | ||
| QuicAddr listenAddr = MsQuicParameterHelpers.GetQuicAddrParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS); | ||
| int port = BinaryPrimitives.ReadUInt16BigEndian(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref listenAddr.Ipv4.sin_port, 1))); | ||
| return new IPEndPoint(listenEndPoint.Address, port); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are you sure that we always gonna get back the exact same listening address as we passed in?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have checked MsQuic source code, and they only change the port (if it was 0)
| } | ||
|
|
||
| // return the actual bound address, including a port. Since the address family may be Unspecified, | ||
| // we cannot use GetIPEndPointParam. We have to manually read the port from the raw QuicAddr structure. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could pass in optional param ipEndPoint.AddressFamily and in case msquic returns QUIC_ADDRESS_FAMILY_UNSPEC we would used it, wdyt?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
you mean add optional parameter to GetIPEndPointParam? that seems to work. Thanks.
| pool: | ||
| name: NetCore1ESPool-Public | ||
| demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open | ||
| demands: ImageOverride -equals 1es-ubuntu-1804-open |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this solving the issue with not enough space? Or why this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, it was suggested to me in dotnet/arcade#9349
The test tries to connect to
Even if you use |
Works with sockets though, I just tested it. It'll connect to IPv6 loopback if you pass IPv6Any to client connect, the same for IPv4Any. |
|
It does not seem to work for me using System.Net.Sockets;
using System.Net;
int port = 5004;
Socket listener = new Socket(SocketType.Stream, ProtocolType.Tcp);
listener.DualMode = true;
listener.Bind(new IPEndPoint(IPAddress.IPv6Any, port));
listener.Listen();
Console.WriteLine($"Listening on {listener.LocalEndPoint}");
Console.WriteLine($"IsDualMode: {listener.DualMode}");
Console.WriteLine($"IPV6_V6Only: {listener.GetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only)}");
using Socket clientIpv6 = new Socket(AddressFamily.InterNetworkV6, SocketType.Stream, ProtocolType.Tcp);
Console.WriteLine($"clientIpv6.DualMode == {clientIpv6.DualMode}");
await clientIpv6.ConnectAsync(new IPEndPoint(IPAddress.IPv6Any, port));
Console.WriteLine($"IsDualMode: {listener.DualMode}");
Console.WriteLine($"IPV6_V6Only: {listener.GetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.IPv6Only)}"); |
|
Works for me: class Program
{
static async Task Main(string[] args)
{
await Task.WhenAll(RunServer(), RunClient());
}
static readonly TaskCompletionSource<IPEndPoint> serverEndpoint = new TaskCompletionSource<IPEndPoint>();
static async Task RunClient()
{
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
var endpoint = await serverEndpoint.Task;
Console.WriteLine("Client connecting to: " + endpoint);
socket.Connect(endpoint);
Console.WriteLine("Client connected to: " + socket.RemoteEndPoint);
var stream = new NetworkStream(socket, ownsSocket: true);
await stream.WriteAsync(UTF8Encoding.UTF8.GetBytes("Ahoj"));
var buffer = new byte[100];
int readBytes;
while ((readBytes = await stream.ReadAsync(buffer)) > 0)
{
Console.WriteLine("Client:" + UTF8Encoding.UTF8.GetString(buffer, 0, readBytes));
}
Console.WriteLine("Client:" + readBytes);
}
static async Task RunServer()
{
var listenSocket = new Socket(SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(new IPEndPoint(IPAddress.IPv6Any, 0));
listenSocket.Listen();
serverEndpoint.SetResult(listenSocket.LocalEndPoint as IPEndPoint);
Console.WriteLine("Server listening on: " + listenSocket.LocalEndPoint);
var socket = await listenSocket.AcceptAsync().ConfigureAwait(false);
var stream = new NetworkStream(socket, ownsSocket: true);
var buffer = new byte[100];
int readBytes = await stream.ReadAsync(buffer);
Console.WriteLine("Server:" + UTF8Encoding.UTF8.GetString(buffer, 0, readBytes));
stream.Dispose();
}
} |
|
Your code runs for me as well: This smells like platform inconsistency with sockets. And the question is what behavior is correct. |
|
That would be probably a difference between Windows and Linux networking stack. It runs for me in WSL |
ManickaP
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, we can finish the discussion about sockets offline 😄 Thanks!
Fixes #67442, #68958.
This PR builds latest MsQuic inside the http stress docker image.
It also adds special case for listening on IPv6Any to accept connections from all IP addresses (i.e. including IPv4), which does not happen by default (see microsoft/msquic#2704).