Skip to content
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

add basic support for SNI different than URI in H3 #71428

Merged
merged 2 commits into from
Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/libraries/System.Net.Quic/src/System.Net.Quic.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@
<Reference Include="System.Console" Condition="'$(Configuration)' == 'Debug'" />
<Reference Include="System.Diagnostics.Tracing" />
<Reference Include="System.Memory" />
<Reference Include="System.Net.NameResolution" />
<Reference Include="System.Net.Primitives" />
<Reference Include="System.Net.Sockets" />
<Reference Include="System.Runtime" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

using System.Diagnostics;
using System.Net.Quic.Implementations.MsQuic.Internal;
using System.Net;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
Expand Down Expand Up @@ -536,15 +537,32 @@ internal unsafe ValueTask ConnectAsync(CancellationToken cancellationToken = def
// 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))
if (!dnsHost.Equals(_state.TargetHost, StringComparison.InvariantCultureIgnoreCase) && !string.IsNullOrEmpty(_state.TargetHost))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason to swap the IsNullOrEmpty check with the equality comparison?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not really. I think _state.TargetHost is unlikely to be null. (or maybe never would)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was just asking because you swapped them, I don't mind either way.

{
// This is form of IPAddress and _state.TargetHost is set to different string
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
MsQuicParameterHelpers.SetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_CONN_REMOTE_ADDRESS, new IPEndPoint(address, port));
targetHost = _state.TargetHost!;
if (IPAddress.TryParse(dnsHost, out IPAddress? address))
{
// This is form of IPAddress and _state.TargetHost is set to different string
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
MsQuicParameterHelpers.SetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_CONN_REMOTE_ADDRESS, new IPEndPoint(address, port));
}
else
{
IPAddress[] addresses = Dns.GetHostAddressesAsync(dnsHost, cancellationToken).GetAwaiter().GetResult();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're in an async method, you can await or you can just use the sync version of the method.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While the name is ConnectAsync it does not have async keyword. I was thinking about sync method but that does not have cancellation overload.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that using sync-over-async on an async method within an async method (albeit only task returning atm) is the best option here.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can switch to Dns.GetHostAddresses and give up on cancellation completely. cc: @stephentoub for any additional suggestions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Once I merge this with the static ConnectAsync, I can take the full advantage of the async and call await. So I guess it doesn't matter much here atm.

cancellationToken.ThrowIfCancellationRequested();
ManickaP marked this conversation as resolved.
Show resolved Hide resolved
if (addresses.Length == 0)
{
throw new SocketException((int)SocketError.HostNotFound);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

QuicException?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is what we get in HTTP/Sockets and in case GetHostAddressesAsync fail to resolve. I'm not 100% we would ever get empty list without failure but if we would I did not want to cause index errors. I'm open to QuicException but fail to resolve may not be IOException.

Copy link
Member

@ManickaP ManickaP Jun 29, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fail to resolve may not be IOException

Why?

We should then reconsider inheriting QuicException from IOException, but we shouldn't throw SocketException from S.N.Quic.

cc @rzikm

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as I mentioned we may do it anyway if Dns.GetHostAddressesAsync fails. Unless you want to catch it and throw something else in both cases. We can certainly do it as well.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Baaah, I'm not sure, let's keep the SocketException for now to be consistent with NameResolution class. We can revisit this in the future if it'll cause confusion.

}
Debug.Assert(!Monitor.IsEntered(_state), "!Monitor.IsEntered(_state)");
// We can do something better than just using first IP but that is what
// MsQuic does today anyway.
MsQuicParameterHelpers.SetIPEndPointParam(MsQuicApi.Api, _state.Handle, QUIC_PARAM_CONN_REMOTE_ADDRESS, new IPEndPoint(addresses[0], port));
}
}
else
{
// We defer everything to MsQuic.
targetHost = dnsHost;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,10 @@ public async Task ConnectWithServerCertificateCallback()
serverConnection.Dispose();
}

[Fact]
public async Task ConnectWithIpSetsSni()
[Theory]
[InlineData("127.0.0.1")]
[InlineData("localhost")]
public async Task ConnectWithIpSetsSni(string destination)
{
X509Certificate2 certificate = System.Net.Test.Common.Configuration.Certificates.GetServerCertificate();
string expectedName = "foobar";
Expand All @@ -274,7 +276,7 @@ public async Task ConnectWithIpSetsSni()

QuicClientConnectionOptions clientOptions = CreateQuicClientOptions();
clientOptions.ClientAuthenticationOptions.TargetHost = expectedName;
clientOptions.RemoteEndPoint = new DnsEndPoint("127.0.0.1", listener.ListenEndPoint.Port);
clientOptions.RemoteEndPoint = new DnsEndPoint(destination, listener.ListenEndPoint.Port);

(QuicConnection clientConnection, QuicConnection serverConnection) = await CreateConnectedQuicConnection(clientOptions, listener);
Assert.Equal(expectedName, receivedHostName);
Expand Down