From 42af636bde677bef5360c926ae2e7190a2f75830 Mon Sep 17 00:00:00 2001 From: Tomas Weinfurt Date: Fri, 13 Aug 2021 10:54:56 -0700 Subject: [PATCH] improve SNI handling when IP is passed in as DnsEndPoint to QuicListener (#57255) --- .../MsQuic/MsQuicConnection.cs | 23 ++++++++++++++-- .../Implementations/MsQuic/MsQuicListener.cs | 5 ++++ .../tests/FunctionalTests/MsQuicTests.cs | 27 +++++++++++++++++++ 3 files changed, 53 insertions(+), 2 deletions(-) diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs index a02417ff710c3..1eba27e5edeb2 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicConnection.cs @@ -599,9 +599,28 @@ internal override ValueTask ConnectAsync(CancellationToken cancellationToken = d } else if (_remoteEndPoint is DnsEndPoint) { - // We don't have way how to set separate SNI and name for connection at this moment. - targetHost = ((DnsEndPoint)_remoteEndPoint).Host; port = ((DnsEndPoint)_remoteEndPoint).Port; + string dnsHost = ((DnsEndPoint)_remoteEndPoint).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 + // who want to bypass DNS but connect to specific virtual host. + 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)); + 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."); + } + targetHost = _state.TargetHost!; + } + else + { + targetHost = dnsHost; + } } else { diff --git a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs index f163c8d161b0b..707f8223f116d 100644 --- a/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs +++ b/src/libraries/System.Net.Quic/src/System/Net/Quic/Implementations/MsQuic/MsQuicListener.cs @@ -76,6 +76,11 @@ public State(QuicListenerOptions options) internal MsQuicListener(QuicListenerOptions options) { + if (options.ListenEndPoint == null) + { + throw new ArgumentNullException(nameof(options.ListenEndPoint)); + } + _state = new State(options); _stateHandle = GCHandle.Alloc(_state); try diff --git a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs index 7dc0e474d0a84..253707dea2688 100644 --- a/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs +++ b/src/libraries/System.Net.Quic/tests/FunctionalTests/MsQuicTests.cs @@ -200,6 +200,33 @@ public async Task ConnectWithCertificateCallback() serverConnection.Dispose(); } + [Fact] + public async Task ConnectWithIpSetsSni() + { + X509Certificate2 certificate = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate(); + string expectedName = "foobar"; + string? receivedHostName = null; + + var listenerOptions = CreateQuicListenerOptions(); + listenerOptions.ServerAuthenticationOptions.ServerCertificate = null; + listenerOptions.ServerAuthenticationOptions.ServerCertificateSelectionCallback = (sender, hostName) => + { + receivedHostName = hostName; + return certificate; + }; + + using QuicListener listener = new QuicListener(QuicImplementationProviders.MsQuic, listenerOptions); + + QuicClientConnectionOptions clientOptions = CreateQuicClientOptions(); + clientOptions.ClientAuthenticationOptions.TargetHost = expectedName; + clientOptions.RemoteEndPoint = new DnsEndPoint("127.0.0.1", listener.ListenEndPoint.Port); + + (QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener); + Assert.Equal(expectedName, receivedHostName); + clientConnection.Dispose(); + serverConnection.Dispose(); + } + [Fact] public async Task ConnectWithCertificateForDifferentName_Throws() {