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

[HTTP/3] SNI does not use the Host header value #57169

Closed
Tratcher opened this issue Aug 10, 2021 · 14 comments · Fixed by #71428
Closed

[HTTP/3] SNI does not use the Host header value #57169

Tratcher opened this issue Aug 10, 2021 · 14 comments · Fixed by #71428
Assignees
Milestone

Comments

@Tratcher
Copy link
Member

Tratcher commented Aug 10, 2021

HttpClient HTTP/3's implementation does not use HttpRequestMessage.Headers.Host for SNI, unlike prior HTTP versions.

            var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:443/");
            request.Version = HttpVersion.Version30;
            request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
            request.Headers.Host = "testhost";

Expected SNI: "testhost"
Actual SNI: "localhost"

Priority: Medium, common customer requirement, difficult to work around.

@dotnet-issue-labeler dotnet-issue-labeler bot added the untriaged New issue has not been triaged by the area owner label Aug 10, 2021
@ghost
Copy link

ghost commented Aug 10, 2021

Tagging subscribers to this area: @dotnet/ncl
See info in area-owners.md if you want to be subscribed.

Issue Details

HttpClient HTTP/3's implementation does not use HttpRequestMessage.Headers.Host for SNI, unlike prior HTTP versions.

            var request = new HttpRequestMessage(HttpMethod.Get, "https://localhost:443/");
            request.Version = HttpVersion.Version30;
            request.VersionPolicy = HttpVersionPolicy.RequestVersionExact;
            request.Headers.Host = "testhost";

Expected SNI: "testhost"
Actual SNI: "localhost"

Priority: Medium, common customer requirement, difficult to work around.

Author: Tratcher
Assignees: -
Labels:

area-System.Net.Http

Milestone: -

@ManickaP
Copy link
Member

@wfurt I think there are some limitation in msquic around SNI, do I remember right?

@ManickaP ManickaP removed the untriaged New issue has not been triaged by the area owner label Aug 10, 2021
@ManickaP ManickaP added this to the Future milestone Aug 10, 2021
@wfurt
Copy link
Member

wfurt commented Aug 10, 2021

We should check if HttpClient sets ClientAuthenticationOptions.TargetHost as it should. Quic will fall-back to endpoint if not set.

@ManickaP
Copy link
Member

ManickaP commented Aug 10, 2021

Then this might be easy to fix, we might reconsider this if we have spare time.

@wfurt
Copy link
Member

wfurt commented Aug 10, 2021

Ok. It is actually

if (_remoteEndPoint is IPEndPoint)
{
SOCKADDR_INET address = MsQuicAddressHelpers.IPEndPointToINet((IPEndPoint)_remoteEndPoint);
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*)&address);
QuicExceptionHelpers.ThrowIfFailed(status, "Failed to connect to peer.");
}
targetHost = _state.TargetHost ?? ((IPEndPoint)_remoteEndPoint).Address.ToString();
port = ((IPEndPoint)_remoteEndPoint).Port;
}
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;
}

Unless @nibanks has suggestion I don't know how to connect to given host but set separate SNI.
(We have fixup code if IP address is used)

@nibanks
Copy link

nibanks commented Aug 10, 2021

For MsQuic, you must pass the SNI you want in the TLS handshake in ConnectionStart.

@Tratcher
Copy link
Member Author

(We have fixup code if IP address is used)

I don't see this fixup happening, I'm getting a blank SNI on the server when an IP is used. That's how I found this issue.

@wfurt
Copy link
Member

wfurt commented Aug 10, 2021

That is not the point. In this case the host you connecting to is different than the SNI.
For IP, we set QUIC_PARAM_CONN.REMOTE_ADDRESS and pass the desired SNI to ConnectionStart @nibanks.
I don't know how to do that for name.

And It is possible HttpClient converts the IP to string and then it comes to Quc as DnsEndPoint with IP string.
I will check.

@wfurt
Copy link
Member

wfurt commented Aug 10, 2021

quicConnection = await ConnectHelper.ConnectQuicAsync(request, Settings._quicImplementationProvider ?? QuicImplementationProviders.Default, new DnsEndPoint(authority.IdnHost, authority.Port), _sslOptionsHttp3!, cancellationToken).ConfigureAwait(false);

So with HttpClient, we would never hit the IPEndPoint case above. I did quick PoC and we can fix it at MsQuciConnection

+ if (!string.IsNullOrEmpty(_state.TargetHost) && !dnsHost.Equals(_state.TargetHost, StringComparison.InvariantCultureIgnoreCase) && IPAddress.TryParse(((DnsEndPoint)_remoteEndPoint).Host!, out IPAddress? address))
+ {

and I can possibly fix it for cases when URI is set to address and Host header is set to different value. (_state.TargetHost)

Alternatively we would need to fix the code around HttpAuthority to pass in IPEndPoint when possible.
Not sure if that worth of the effort @Tratcher .

@Tratcher
Copy link
Member Author

and I can possibly fix it for cases when URI is set to address and Host header is set to different value. (_state.TargetHost)

I think this is the primary use case, people bypassing DNS.

Eventually it should behave the same as HTTP/1.1 to avoid breaking people as they move up.

@karelz
Copy link
Member

karelz commented Nov 16, 2021

Triage: This will likely require API changes in msquic

@karelz karelz modified the milestones: Future, 7.0.0 Nov 16, 2021
@nibanks
Copy link

nibanks commented Feb 16, 2022

The design MsQuic currently has, and how HTTP.sys uses it, is that if you want to target a different IP than what the SNI points to, you'd resolve the name to whatever IP you choose and then set the remote address to that before calling ConnectionStart. We have no way to use two different host names.

@wfurt
Copy link
Member

wfurt commented Feb 16, 2022

right, I understand this is current limitation. We can do resolution in .NET but then can we give you list of addresses? It seems suboptimal top duplicate any fall-back logic. And make it same with case when the SNI is same.

Also it seems like it would be easy to split SNI and hostanke within MsQuic and track them separately - and in common case have them same.

@ManickaP
Copy link
Member

Triage: msquic only connects to the first IP to which the hostname resolves and doesn't do any fallbacks. Nowadays we have this fallback on the managed layer in Sockets, doing it in S.N.Quic would be equivalent to that. So we should do the resolution on our side, set remote IP in msquic, and SNI separately. No work for msquic in this, just for us.

@ghost ghost added the in-pr There is an active PR which will close this issue when it is merged label Jun 29, 2022
@ghost ghost removed the in-pr There is an active PR which will close this issue when it is merged label Jul 1, 2022
@ghost ghost locked as resolved and limited conversation to collaborators Jul 31, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants