<data name="net_quic_tls_version_notsupported" xml:space="preserve">
<value>Could not use a TLS version required by Quic. TLS 1.3 may have been disabled in the registry.</value>
</data>
+ <!-- Referenced in shared IPEndPointExtensions.cs-->
+ <data name="net_InvalidAddressFamily" xml:space="preserve">
+ <value>The AddressFamily {0} is not valid for the {1} end point, use {2} instead.</value>
+ </data>
+ <data name="net_InvalidSocketAddressSize" xml:space="preserve">
+ <value>The supplied {0} is an invalid size for the {1} end point.</value>
+ </data>
</root>
<Compile Include="$(CommonPath)System\Net\MultiArrayBuffer.cs" Link="Common\System\Net\MultiArrayBuffer.cs" />
<Compile Include="$(CommonPath)System\Net\Logging\NetEventSource.Common.cs" Link="Common\System\Net\Logging\NetEventSource.Common.cs" />
<Compile Include="$(CommonPath)System\Net\StreamBuffer.cs" Link="Common\System\Net\StreamBuffer.cs" />
+ <Compile Include="$(CommonPath)System\Net\SocketAddress.cs" Link="Common\System\Net\SocketAddress.cs" />
+ <Compile Include="$(CommonPath)System\Net\IPAddressParserStatics.cs" Link="Common\System\Net\IPAddressParserStatics.cs" />
+ <!-- System.Net.Internals -->
+ <Compile Include="$(CommonPath)System\Net\Internals\IPEndPointExtensions.cs" Link="Common\System\Net\Internals\IPEndPointExtensions.cs" />
</ItemGroup>
<!-- Unsupported platforms -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == ''">
<Compile Include="$(CommonPath)Interop\Windows\Crypt32\Interop.MsgEncodingType.cs" Link="Common\Interop\Windows\Crypt32\Interop.Interop.MsgEncodingType.cs" />
<Compile Include="$(CommonPath)Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" Link="Common\Interop\Windows\SChannel\Interop.SECURITY_STATUS.cs" />
<Compile Include="$(CommonPath)System\Net\Security\CertificateValidation.Windows.cs" Link="Common\System\Net\Security\CertificateValidation.Windows.cs" />
+ <Compile Include="$(CommonPath)System\Net\SocketAddressPal.Windows.cs" Link="Common\System\Net\SocketAddressPal.Windows.cs" />
<Compile Include="System\Net\Quic\Implementations\MsQuic\Interop\MsQuicStatusCodes.Windows.cs" />
</ItemGroup>
<!-- Unix (OSX + Linux) specific files -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Linux' or '$(TargetPlatformIdentifier)' == 'OSX' or '$(TargetPlatformIdentifier)' == 'FreeBSD'">
<Compile Include="$(CommonPath)Interop\Unix\Interop.Libraries.cs" Link="Common\Interop\Unix\Interop.Libraries.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\Interop.Errors.cs" Link="Common\Interop\Unix\Interop.Errors.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ASN1.cs" />
+ <Compile Include="$(CommonPath)Interop\Unix\System.Native\Interop.SocketAddress.cs" Link="Common\Interop\Unix\System.Native\Interop.SocketAddress.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.BIO.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.ERR.cs" />
<Compile Include="$(CommonPath)Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" Link="Common\Interop\Unix\System.Security.Cryptography.Native\Interop.Initialization.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeBioHandle.Unix.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" Link="Common\Microsoft\Win32\SafeHandles\Asn1SafeHandles.Unix.cs" />
<Compile Include="$(CommonPath)Microsoft\Win32\SafeHandles\SafeHandleCache.cs" Link="Common\Microsoft\Win32\SafeHandles\SafeHandleCache.cs" />
+ <Compile Include="$(CommonPath)System\Net\SocketAddressPal.Unix.cs" Link="Common\System\Net\SocketAddressPal.Unix.cs" />
</ItemGroup>
<!-- Linux specific files -->
<ItemGroup Condition="'$(TargetPlatformIdentifier)' == 'Linux'">
</ItemGroup>
<ItemGroup>
+ <Reference Include="Microsoft.Win32.Primitives" />
<Reference Include="System.Collections" />
<Reference Include="System.Collections.Concurrent" />
<Reference Include="System.Collections.NonGeneric" />
internal MockListener(QuicListenerOptions options)
{
- if (options.ListenEndPoint is null || options.ListenEndPoint.Address != IPAddress.Loopback || options.ListenEndPoint.Port != 0)
+ if (options.ListenEndPoint is null || (options.ListenEndPoint.Address != IPAddress.Loopback && options.ListenEndPoint.Address != IPAddress.IPv6Loopback) || options.ListenEndPoint.Port != 0)
{
throw new ArgumentException("Must pass loopback address and port 0");
}
{
internal static class MsQuicAddressHelpers
{
- internal static unsafe IPEndPoint INetToIPEndPoint(ref SOCKADDR_INET inetAddress)
+ internal static unsafe IPEndPoint INetToIPEndPoint(IntPtr pInetAddress)
{
- if (inetAddress.si_family == QUIC_ADDRESS_FAMILY.INET)
- {
- return new IPEndPoint(new IPAddress(MemoryMarshal.CreateReadOnlySpan<byte>(ref inetAddress.Ipv4.sin_addr[0], 4)), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv4.sin_port));
- }
- else
- {
- return new IPEndPoint(new IPAddress(MemoryMarshal.CreateReadOnlySpan<byte>(ref inetAddress.Ipv6.sin6_addr[0], 16)), (ushort)IPAddress.NetworkToHostOrder((short)inetAddress.Ipv6.sin6_port));
- }
- }
-
- internal static unsafe SOCKADDR_INET IPEndPointToINet(IPEndPoint endpoint)
- {
- SOCKADDR_INET socketAddress = default;
- if (!endpoint.Address.Equals(IPAddress.Any) && !endpoint.Address.Equals(IPAddress.IPv6Any))
- {
- switch (endpoint.Address.AddressFamily)
- {
- case AddressFamily.InterNetwork:
- endpoint.Address.TryWriteBytes(MemoryMarshal.CreateSpan<byte>(ref socketAddress.Ipv4.sin_addr[0], 4), out _);
- socketAddress.Ipv4.sin_family = QUIC_ADDRESS_FAMILY.INET;
- break;
- case AddressFamily.InterNetworkV6:
- endpoint.Address.TryWriteBytes(MemoryMarshal.CreateSpan<byte>(ref socketAddress.Ipv6.sin6_addr[0], 16), out _);
- socketAddress.Ipv6.sin6_family = QUIC_ADDRESS_FAMILY.INET6;
- break;
- default:
- throw new ArgumentException(SR.net_quic_addressfamily_notsupported);
- }
- }
-
- SetPort(endpoint.Address.AddressFamily, ref socketAddress, endpoint.Port);
- return socketAddress;
- }
-
- private static void SetPort(AddressFamily addressFamily, ref SOCKADDR_INET socketAddrInet, int originalPort)
- {
- ushort convertedPort = (ushort)IPAddress.HostToNetworkOrder((short)originalPort);
- socketAddrInet.Ipv4.sin_port = convertedPort;
+ // MsQuic always uses storage size as if IPv6 was used
+ Span<byte> addressBytes = new Span<byte>((byte*)pInetAddress, Internals.SocketAddress.IPv6AddressSize);
+ return new Internals.SocketAddress(SocketAddressPal.GetAddressFamily(addressBytes), addressBytes).GetIPEndPoint();
}
}
}
using System.Diagnostics;
using System.Runtime.InteropServices;
+using System.Net.Sockets;
using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
namespace System.Net.Quic.Implementations.MsQuic.Internal
{
internal static class MsQuicParameterHelpers
{
- internal static unsafe SOCKADDR_INET GetINetParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param)
+ internal static unsafe IPEndPoint GetIPEndPointParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param)
{
- SOCKADDR_INET value;
- uint valueLen = (uint)sizeof(SOCKADDR_INET);
+ // MsQuic always uses storage size as if IPv6 was used
+ uint valueLen = (uint)Internals.SocketAddress.IPv6AddressSize;
+ Span<byte> address = stackalloc byte[Internals.SocketAddress.IPv6AddressSize];
- uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, (byte*)&value);
- QuicExceptionHelpers.ThrowIfFailed(status, "GetINETParam failed.");
- Debug.Assert(valueLen == sizeof(SOCKADDR_INET));
+ fixed (byte* paddress = &MemoryMarshal.GetReference(address))
+ {
+ uint status = api.GetParamDelegate(nativeObject, level, param, ref valueLen, paddress);
+ QuicExceptionHelpers.ThrowIfFailed(status, "GetIPEndPointParam failed.");
+ }
- return value;
+ address = address.Slice(0, (int)valueLen);
+
+ return new Internals.SocketAddress(SocketAddressPal.GetAddressFamily(address), address)
+ .GetIPEndPoint();
+ }
+
+ internal static unsafe void SetIPEndPointParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param, IPEndPoint value)
+ {
+ Internals.SocketAddress socketAddress = IPEndPointExtensions.Serialize(value);
+
+ // MsQuic always reads same amount of memory as if IPv6 was used, so we can't pass pointer to socketAddress.Buffer directly
+ Span<byte> address = stackalloc byte[Internals.SocketAddress.IPv6AddressSize];
+ socketAddress.Buffer.AsSpan(0, socketAddress.Size).CopyTo(address);
+ address.Slice(socketAddress.Size).Clear();
+
+ fixed (byte* paddress = &MemoryMarshal.GetReference(address))
+ {
+ QuicExceptionHelpers.ThrowIfFailed(
+ api.SetParamDelegate(nativeObject, level, param, (uint)address.Length, paddress),
+ "Could not set IPEndPoint");
+ }
}
internal static unsafe ushort GetUShortParam(MsQuicApi api, SafeHandle nativeObject, QUIC_PARAM_LEVEL level, uint param)
SafeMsQuicListenerHandle listener,
QuicBuffer* alpnBuffers,
uint alpnBufferCount,
- ref SOCKADDR_INET localAddress);
+ byte* localAddress);
internal delegate void ListenerStopDelegate(
SafeMsQuicListenerHandle listener);
internal StreamEventDataUnion Data;
}
- // TODO: rename to C#-like
- [StructLayout(LayoutKind.Sequential)]
- internal struct SOCKADDR_IN
- {
-#if SOCKADDR_HAS_LENGTH
- internal byte sin_len;
-#endif
- internal QUIC_ADDRESS_FAMILY sin_family;
- internal ushort sin_port;
- internal fixed byte sin_addr[4];
- }
-
- // TODO: rename to C#-like
- [StructLayout(LayoutKind.Sequential)]
- internal struct SOCKADDR_IN6
- {
-#if SOCKADDR_HAS_LENGTH
- internal byte sin6_len;
-#endif
- internal QUIC_ADDRESS_FAMILY sin6_family;
- internal ushort sin6_port;
- internal uint sin6_flowinfo;
- internal fixed byte sin6_addr[16];
- internal uint sin6_scope_id;
- }
-
- // TODO: rename to C#-like
- [StructLayout(LayoutKind.Explicit)]
- internal struct SOCKADDR_INET
- {
- [FieldOffset(0)]
- internal SOCKADDR_IN Ipv4;
- [FieldOffset(0)]
- internal SOCKADDR_IN6 Ipv6;
-#if SOCKADDR_HAS_LENGTH
- [FieldOffset(1)]
-#else
- [FieldOffset(0)]
-#endif
- internal QUIC_ADDRESS_FAMILY si_family;
- }
-
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate uint StreamCallbackDelegate(
IntPtr stream,
return __retVal;
}
- internal uint ListenerStart(SafeMsQuicListenerHandle listener, QuicBuffer* alpnBuffers, uint alpnBufferCount, ref SOCKADDR_INET localAddress)
+ internal uint ListenerStart(SafeMsQuicListenerHandle listener, QuicBuffer* alpnBuffers, uint alpnBufferCount, byte* localAddress)
{
- IntPtr __listener_gen_native = default;
+ IntPtr __listener_gen_native;
uint __retVal;
//
// Setup
//
listener.DangerousAddRef(ref listener__addRefd);
__listener_gen_native = listener.DangerousGetHandle();
- fixed (SOCKADDR_INET* __localAddress_gen_native = &localAddress)
- {
- __retVal = ((delegate* unmanaged[Cdecl]<IntPtr, QuicBuffer*, uint, SOCKADDR_INET*, uint>)_functionPointer)(__listener_gen_native, alpnBuffers, alpnBufferCount, __localAddress_gen_native);
- }
+ __retVal = ((delegate* unmanaged[Cdecl]<IntPtr, QuicBuffer*, uint, byte*, uint>)_functionPointer)(__listener_gen_native, alpnBuffers, alpnBufferCount, localAddress);
}
finally
{
{
// Connected will already be true for connections accepted from a listener.
Debug.Assert(!Monitor.IsEntered(state));
- SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
+
Debug.Assert(state.Connection != null);
- state.Connection._localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress);
+ state.Connection._localEndPoint = MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.LOCAL_ADDRESS);
state.Connection.SetNegotiatedAlpn(connectionEvent.Data.Connected.NegotiatedAlpn, connectionEvent.Data.Connected.NegotiatedAlpnLength);
state.Connection = null;
string targetHost;
int port;
- if (_remoteEndPoint is IPEndPoint)
+ if (_remoteEndPoint is IPEndPoint ipEndPoint)
{
- SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet((IPEndPoint)_remoteEndPoint);
- unsafe
- {
- Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
- status = MsQuicApi.Api.SetParamDelegate(_state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.REMOTE_ADDRESS, (uint)sizeof(SOCKADDR_INET), (byte*)&address);
- QuicExceptionHelpers.ThrowIfFailed(status, "Failed to connect to peer.");
- }
-
+ Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
+ MsQuicParameterHelpers.SetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.REMOTE_ADDRESS, ipEndPoint);
targetHost = _state.TargetHost ?? ((IPEndPoint)_remoteEndPoint).Address.ToString();
port = ((IPEndPoint)_remoteEndPoint).Port;
}
- else if (_remoteEndPoint is DnsEndPoint)
+ else if (_remoteEndPoint is DnsEndPoint dnsEndPoint)
{
- port = ((DnsEndPoint)_remoteEndPoint).Port;
- string dnsHost = ((DnsEndPoint)_remoteEndPoint).Host!;
+ port = dnsEndPoint.Port;
+ string dnsHost = dnsEndPoint.Host!;
// We don't have way how to set separate SNI and name for connection at this moment.
// If the name is actually IP address we can use it to make at least some cases work for people
if (!string.IsNullOrEmpty(_state.TargetHost) && !dnsHost.Equals(_state.TargetHost, StringComparison.InvariantCultureIgnoreCase) && IPAddress.TryParse(dnsHost, out IPAddress? address))
{
// This is form of IPAddress and _state.TargetHost is set to different string
- SOCKADDR_INET quicAddress = MsQuicAddressHelpers.IPEndPointToINet(new IPEndPoint(address, port));
- unsafe
- {
- Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
- status = MsQuicApi.Api.SetParamDelegate(_state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.REMOTE_ADDRESS, (uint)sizeof(SOCKADDR_INET), (byte*)&quicAddress);
- QuicExceptionHelpers.ThrowIfFailed(status, "Failed to connect to peer.");
- }
+ Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
+ MsQuicParameterHelpers.SetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.CONNECTION, (uint)QUIC_PARAM_CONN.REMOTE_ADDRESS, new IPEndPoint(address, port));
targetHost = _state.TargetHost!;
}
else
using System.Threading.Channels;
using System.Threading.Tasks;
using static System.Net.Quic.Implementations.MsQuic.Internal.MsQuicNativeMethods;
+using System.Net.Sockets;
namespace System.Net.Quic.Implementations.MsQuic
{
List<SslApplicationProtocol> applicationProtocols = options.ServerAuthenticationOptions!.ApplicationProtocols!;
IPEndPoint listenEndPoint = options.ListenEndPoint!;
- SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet(listenEndPoint);
+ Internals.SocketAddress address = IPEndPointExtensions.Serialize(listenEndPoint);
uint status;
{
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
MsQuicAlpnHelper.Prepare(applicationProtocols, out handles, out buffers);
- status = MsQuicApi.Api.ListenerStartDelegate(_state.Handle, (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)applicationProtocols.Count, ref address);
+ fixed (byte* paddress = address.Buffer)
+ {
+ status = MsQuicApi.Api.ListenerStartDelegate(_state.Handle, (QuicBuffer*)Marshal.UnsafeAddrOfPinnedArrayElement(buffers, 0), (uint)applicationProtocols.Count, paddress);
+ }
}
catch
{
QuicExceptionHelpers.ThrowIfFailed(status, "ListenerStart failed.");
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
- SOCKADDR_INET inetAddress = MsQuicParameterHelpers.GetINetParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS);
- return MsQuicAddressHelpers.INetToIPEndPoint(ref inetAddress);
+ return MsQuicParameterHelpers.GetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_LEVEL.LISTENER, (uint)QUIC_PARAM_LISTENER.LOCAL_ADDRESS);
}
private void Stop()
{
ref NewConnectionInfo connectionInfo = ref *evt->Data.NewConnection.Info;
- IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.LocalAddress);
- IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(ref *(SOCKADDR_INET*)connectionInfo.RemoteAddress);
+ IPEndPoint localEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(connectionInfo.LocalAddress);
+ IPEndPoint remoteEndPoint = MsQuicAddressHelpers.INetToIPEndPoint(connectionInfo.RemoteAddress);
+
string targetHost = string.Empty; // compat with SslStream
if (connectionInfo.ServerNameLength > 0 && connectionInfo.ServerName != IntPtr.Zero)
{
using System.Linq;
using System.Net.Security;
using System.Net.Sockets;
+using System.Runtime.InteropServices;
using System.Security.Authentication;
using System.Security.Cryptography;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
+using Microsoft.DotNet.XUnitExtensions;
using Xunit;
using Xunit.Abstractions;
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/67301", TestPlatforms.Linux)]
public async Task CertificateCallbackThrowPropagates()
{
using CancellationTokenSource cts = new CancellationTokenSource(PassingTestTimeout);
}
[Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/67301", TestPlatforms.Linux)]
public async Task ConnectWithCertificateCallback()
{
X509Certificate2 c1 = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate();
public async Task ConnectWithCertificateForLoopbackIP_IndicatesExpectedError(string ipString, bool expectsError)
{
var ipAddress = IPAddress.Parse(ipString);
+
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux) && ipAddress.AddressFamily == AddressFamily.InterNetworkV6)
+ {
+ // [ActiveIssue("https://github.com/dotnet/runtime/issues/67301")]
+ throw new SkipTestException("IPv6 on Linux is temporarily broken");
+ }
+
(X509Certificate2 certificate, _) = System.Net.Security.Tests.TestHelper.GenerateCertificates(expectsError ? "badhost" : "localhost");
var listenerOptions = new QuicListenerOptions();
await clientStreamTask;
}).WaitAsync(TimeSpan.FromSeconds(6));
}
+
+ [Fact]
+ [ActiveIssue("https://github.com/dotnet/runtime/issues/67301", TestPlatforms.Linux)]
+ public async Task Listener_Backlog_Success_IPv6()
+ {
+ await Task.Run(async () =>
+ {
+ using QuicListener listener = CreateQuicListener(new IPEndPoint(IPAddress.IPv6Loopback, 0));
+
+ using QuicConnection clientConnection = CreateQuicConnection(listener.ListenEndPoint);
+ var clientStreamTask = clientConnection.ConnectAsync();
+
+ using QuicConnection serverConnection = await listener.AcceptConnectionAsync();
+ await clientStreamTask;
+ }).WaitAsync(TimeSpan.FromSeconds(6));
+ }
}
[ConditionalClass(typeof(QuicTestBase<MockProviderFactory>), nameof(QuicTestBase<MockProviderFactory>.IsSupported))]