Skip to content

Commit 44ca4ad

Browse files
committed
Make listening on IPv6Any accept IPv4 connections as well
1 parent bf029fe commit 44ca4ad

File tree

5 files changed

+70
-24
lines changed

5 files changed

+70
-24
lines changed

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

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -83,16 +83,7 @@ public StressServer(Configuration configuration)
8383
{
8484
case "+":
8585
case "*":
86-
// Workaround for a msquic bug: can't connect via IPv4 when listening on IPv6
87-
// https://github.com/microsoft/msquic/issues/2704
88-
if (configuration.HttpVersion == HttpVersion.Version30)
89-
{
90-
ko.Listen(IPAddress.Any, port, ConfigureListenOptions);
91-
}
92-
else
93-
{
94-
ko.ListenAnyIP(port, ConfigureListenOptions);
95-
}
86+
ko.ListenAnyIP(port, ConfigureListenOptions);
9687
break;
9788
default:
9889
IPAddress iPAddress = Dns.GetHostAddresses(hostname).First();

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?
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: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,28 @@
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
{
14+
internal static unsafe QuicAddr GetQuicAddrParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param)
15+
{
16+
QuicAddr value = default;
17+
uint valueLen = (uint)sizeof(QuicAddr);
18+
19+
ThrowIfFailure(api.ApiTable->GetParam(
20+
nativeObject.QuicHandle,
21+
param,
22+
&valueLen,
23+
(byte*)&value), "GetQuicAddrParam failed");
24+
Debug.Assert(valueLen == sizeof(QuicAddr));
25+
26+
return value;
27+
}
28+
1329
internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, MsQuicSafeHandle nativeObject, uint param)
1430
{
1531
// MsQuic always uses storage size as if IPv6 was used

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

Lines changed: 20 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,35 +179,41 @@ 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
{
205206
_stateHandle.Free();
206207
throw;
207208
}
208209

210+
// return the actual bound address, including a port. Since the address family may be Unspecified,
211+
// we cannot use GetIPEndPointParam. We have to manually read the port from the raw QuicAddr structure.
212+
// The actual address is unchanged.
209213
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
210-
return MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS);
214+
QuicAddr listenAddr = MsQuicParameterHelpers.GetQuicAddrParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LISTENER_LOCAL_ADDRESS);
215+
int port = BinaryPrimitives.ReadUInt16BigEndian(MemoryMarshal.AsBytes(MemoryMarshal.CreateSpan(ref listenAddr.Ipv4.sin_port, 1)));
216+
return new IPEndPoint(listenEndPoint.Address, port);
211217
}
212218

213219
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)