Skip to content

Commit 8d4a724

Browse files
authored
Fix HTTP3 stress (#69182)
* Build msquic locally * Use IPv4 addresses only when testing HTTP3 * Code review changes * fixup! Use IPv4 addresses only when testing HTTP3 * Make listening on IPv6Any accept IPv4 connections as well * Minor changes * Don't continue if runtime image build fails * Switch to 1es-ubuntu-1804-open image * Code review feedback
1 parent 5a52a13 commit 8d4a724

File tree

8 files changed

+82
-33
lines changed

8 files changed

+82
-33
lines changed

eng/pipelines/libraries/stress/http.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,21 +31,21 @@ jobs:
3131
DUMPS_SHARE_MOUNT_ROOT: "/dumps-share"
3232
pool:
3333
name: NetCore1ESPool-Public
34-
demands: ImageOverride -equals Build.Ubuntu.1804.Amd64.Open
34+
demands: ImageOverride -equals 1es-ubuntu-1804-open
3535

3636
steps:
3737
- checkout: self
3838
clean: true
3939
fetchDepth: 5
4040

4141
- bash: |
42-
$(dockerfilesFolder)/build-docker-sdk.sh -t $(sdkBaseImage) -c $(BUILD_CONFIGURATION)
42+
$(dockerfilesFolder)/build-docker-sdk.sh -t $(sdkBaseImage) -c $(BUILD_CONFIGURATION) && \
4343
echo "##vso[task.setvariable variable=succeeded;isOutput=true]true"
4444
name: buildRuntime
4545
displayName: Build CLR and Libraries
4646
4747
- bash: |
48-
$(httpStressProject)/run-docker-compose.sh -o -c $(BUILD_CONFIGURATION) -t $(sdkBaseImage)
48+
$(httpStressProject)/run-docker-compose.sh -o -c $(BUILD_CONFIGURATION) -t $(sdkBaseImage) && \
4949
echo "##vso[task.setvariable variable=succeeded;isOutput=true]true"
5050
name: buildStress
5151
displayName: Build HttpStress

src/libraries/Common/src/System/Net/SocketAddress.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ internal SocketAddress(AddressFamily addressFamily, ReadOnlySpan<byte> buffer)
133133
{
134134
Buffer = buffer.ToArray();
135135
InternalSize = Buffer.Length;
136+
SocketAddressPal.SetAddressFamily(Buffer, addressFamily);
136137
}
137138

138139
internal IPAddress GetIPAddress()

src/libraries/System.Net.Http/tests/StressTests/HttpStress/Dockerfile

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
ARG SDK_BASE_IMAGE=mcr.microsoft.com/dotnet/nightly/sdk:6.0-bullseye-slim
22
FROM $SDK_BASE_IMAGE
33

4-
WORKDIR /app
5-
COPY . .
6-
7-
# Pulling the msquic Debian package from packages.microsoft.com
8-
RUN apt-get update -y
9-
RUN apt-get install -y gnupg2 software-properties-common
10-
RUN curl -sSL https://packages.microsoft.com/keys/microsoft.asc | apt-key add -
11-
RUN apt-add-repository https://packages.microsoft.com/debian/11/prod
12-
RUN apt-get update -y
13-
RUN apt-get install -y libmsquic
14-
RUN apt-get upgrade -y
4+
# Build latest msquic locally
5+
WORKDIR /msquic
6+
RUN apt-get update -y && \
7+
apt-get upgrade -y && \
8+
apt-get install -y cmake clang ruby-dev gem lttng-tools libssl-dev && \
9+
gem install fpm
10+
RUN git clone --recursive https://github.com/dotnet/msquic
11+
RUN cd msquic/src/msquic && \
12+
mkdir build && \
13+
cmake -B build -DCMAKE_BUILD_TYPE=Release -DQUIC_ENABLE_LOGGING=false -DQUIC_USE_SYSTEM_LIBCRYPTO=true -DQUIC_BUILD_TOOLS=off -DQUIC_BUILD_TEST=off -DQUIC_BUILD_PERF=off && \
14+
cd build && \
15+
cmake --build . --config Release
16+
RUN cd msquic/src/msquic/build/bin/Release && \
17+
rm libmsquic.so && \
18+
fpm -f -s dir -t deb -n libmsquic -v $( find -type f | cut -d "." -f 4- ) \
19+
--license MIT --url https://github.com/microsoft/msquic --log error \
20+
$( ls ./* | cut -d "/" -f 2 | sed -r "s/(.*)/\1=\/usr\/lib\/\1/g" ) && \
21+
dpkg -i libmsquic_*.deb
1522

1623
ARG VERSION=7.0
1724
ARG CONFIGURATION=Release
1825

26+
# Build the stress server
27+
WORKDIR /app
28+
COPY . .
29+
1930
RUN dotnet build -c $CONFIGURATION \
2031
-p:TargetingPacksTargetsLocation=/live-runtime-artifacts/targetingpacks.targets \
2132
-p:MicrosoftNetCoreAppRefPackDir=/live-runtime-artifacts/microsoft.netcore.app.ref/ \

src/libraries/System.Net.Http/tests/StressTests/HttpStress/StressServer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ void ConfigureListenOptions(ListenOptions listenOptions)
118118
else
119119
{
120120
listenOptions.Protocols =
121-
configuration.HttpVersion == HttpVersion.Version20 ?
121+
configuration.HttpVersion == HttpVersion.Version20 ?
122122
HttpProtocols.Http2 :
123-
HttpProtocols.Http1 ;
123+
HttpProtocols.Http1;
124124
}
125125
}
126126
});
@@ -139,7 +139,8 @@ void ConfigureListenOptions(ListenOptions listenOptions)
139139
try
140140
{
141141
File.Delete(filename);
142-
} catch {}
142+
}
143+
catch { }
143144
}
144145

145146
loggerConfiguration = loggerConfiguration

src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicAddressHelpers.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
using System.Diagnostics;
5+
using System.Net.Sockets;
6+
using System.Runtime.InteropServices;
7+
using Microsoft.Quic;
8+
49
namespace System.Net.Quic.Implementations.MsQuic.Internal
510
{
611
internal static class MsQuicAddressHelpers
@@ -11,5 +16,18 @@ internal static unsafe IPEndPoint INetToIPEndPoint(IntPtr pInetAddress)
1116
Span<byte> addressBytes = new Span<byte>((byte*)pInetAddress, Internals.SocketAddress.IPv6AddressSize);
1217
return new Internals.SocketAddress(SocketAddressPal.GetAddressFamily(addressBytes), addressBytes).GetIPEndPoint();
1318
}
19+
20+
internal static unsafe QuicAddr ToQuicAddr(this IPEndPoint iPEndPoint)
21+
{
22+
// TODO: is the layout same for SocketAddress.Buffer and QuicAddr on all platforms?
23+
QuicAddr result = default;
24+
Span<byte> rawAddress = MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref result, 1));
25+
26+
Internals.SocketAddress address = IPEndPointExtensions.Serialize(iPEndPoint);
27+
Debug.Assert(address.Size <= rawAddress.Length);
28+
29+
address.Buffer.AsSpan(0, address.Size).CopyTo(rawAddress);
30+
return result;
31+
}
1432
}
1533
}

src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/Internal/MsQuicParameterHelpers.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
using System.Diagnostics;
55
using System.Runtime.InteropServices;
66
using System.Net.Sockets;
7+
using Microsoft.Quic;
78
using static Microsoft.Quic.MsQuic;
89

910
namespace System.Net.Quic.Implementations.MsQuic.Internal
1011
{
1112
internal static class MsQuicParameterHelpers
1213
{
13-
internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param)
14+
internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param, AddressFamily? addressFamilyOverride = null)
1415
{
1516
// MsQuic always uses storage size as if IPv6 was used
1617
uint valueLen = (uint)Internals.SocketAddress.IPv6AddressSize;
@@ -27,7 +28,7 @@ internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, MsQuicSafeHa
2728

2829
address = address.Slice(0, (int)valueLen);
2930

30-
return new Internals.SocketAddress(SocketAddressPal.GetAddressFamily(address), address).GetIPEndPoint();
31+
return new Internals.SocketAddress(addressFamilyOverride ?? SocketAddressPal.GetAddressFamily(address), address).GetIPEndPoint();
3132
}
3233

3334
internal static unsafe void SetIPEndPointParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param, IPEndPoint value)

src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Buffers;
5+
using System.Buffers.Binary;
56
using System.Collections.Generic;
67
using System.Collections.Concurrent;
78
using System.Diagnostics;
@@ -12,7 +13,6 @@
1213
using System.Threading;
1314
using System.Threading.Channels;
1415
using System.Threading.Tasks;
15-
using System.Net.Sockets;
1616
using Microsoft.Quic;
1717
using System.Runtime.CompilerServices;
1818
using static Microsoft.Quic.MsQuic;
@@ -179,26 +179,27 @@ private unsafe IPEndPoint Start(QuicListenerOptions options)
179179
List<SslApplicationProtocol> applicationProtocols = options.ServerAuthenticationOptions!.ApplicationProtocols!;
180180
IPEndPoint listenEndPoint = options.ListenEndPoint!;
181181

182-
Internals.SocketAddress address = IPEndPointExtensions.Serialize(listenEndPoint);
183-
184182
Debug.Assert(_stateHandle.IsAllocated);
185183
try
186184
{
187185
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
188186
using var msquicBuffers = new MsQuicBuffers();
189187
msquicBuffers.Initialize(applicationProtocols, applicationProtocol => applicationProtocol.Protocol);
190-
// TODO: is the layout same for SocketAddress.Buffer and QuicAddr?
191-
// TODO: maybe add simple extensions/helpers:
192-
// - QuicAddr ToQuicAddr(this IPEndPoint ipEndPoint)
193-
// - IPEndPoint ToIPEndPoint(this ref QuicAddr quicAddress)
194-
fixed (byte* paddress = address.Buffer)
188+
189+
QuicAddr address = listenEndPoint.ToQuicAddr();
190+
191+
if (listenEndPoint.Address == IPAddress.IPv6Any)
195192
{
196-
ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerStart(
197-
_state.Handle.QuicHandle,
198-
msquicBuffers.Buffers,
199-
(uint)applicationProtocols.Count,
200-
(QuicAddr*)paddress), "ListenerStart failed");
193+
// For IPv6Any, MsQuic would listen only for IPv6 connections. To mimic the behavior of TCP sockets,
194+
// we leave the address family unspecified and let MsQuic handle connections from all IP addresses.
195+
address.Family = QUIC_ADDRESS_FAMILY_UNSPEC;
201196
}
197+
198+
ThrowIfFailure(MsQuicApi.Api.ApiTable->ListenerStart(
199+
_state.Handle.QuicHandle,
200+
msquicBuffers.Buffers,
201+
(uint)applicationProtocols.Count,
202+
&address), "ListenerStart failed");
202203
}
203204
catch
204205
{
@@ -207,7 +208,8 @@ private unsafe IPEndPoint Start(QuicListenerOptions options)
207208
}
208209

209210
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
210-
return MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS);
211+
// override the address family to the original value in case we had to use UNSPEC
212+
return MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS, listenEndPoint.AddressFamily);
211213
}
212214

213215
private unsafe Task StopAsync()

src/libraries/System.Net.Quic/tests/FunctionalTests/QuicListenerTests.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,21 @@ await Task.Run(async () =>
4141
await clientStreamTask;
4242
}).WaitAsync(TimeSpan.FromSeconds(6));
4343
}
44+
45+
[ConditionalFact(nameof(IsMsQuicProvider))]
46+
public async Task Listener_IPv6Any_Accepts_IPv4()
47+
{
48+
await Task.Run(async () =>
49+
{
50+
using QuicListener listener = CreateQuicListener(new IPEndPoint(IPAddress.IPv6Any, 0));
51+
52+
using QuicConnection clientConnection = CreateQuicConnection(new IPEndPoint(IPAddress.Loopback, listener.ListenEndPoint.Port));
53+
var clientStreamTask = clientConnection.ConnectAsync();
54+
55+
using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
56+
await clientStreamTask;
57+
}).WaitAsync(TimeSpan.FromSeconds(6));
58+
}
4459
}
4560

4661
[ConditionalClass(typeof(QuicTestBase<MockProviderFactory>), nameof(QuicTestBase<MockProviderFactory>.IsSupported))]

0 commit comments

Comments
 (0)