-
Notifications
You must be signed in to change notification settings - Fork 4.8k
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
[Question] Custom the server name of SocketsHttpHandler #45144
Comments
Tagging subscribers to this area: @dotnet/ncl Issue DetailsSince the
|
|
Do we have a way to satisfy what I want without WinHttpHandler? After all, I found that WinHttpHandler's debug message is quite Illegible compare to SocketsHttpHandler |
Could you please post repro of what you're trying to achieve? Either your original |
public class DelegatedHttpClientHandler : HttpClientHandler
{
private readonly bool directConnect;
private readonly IHttpRequestInterceptor? interceptor;
public DelegatedHttpClientHandler(IHttpRequestInterceptor? interceptor = null, bool directConnect = true)
{
this.directConnect = directConnect;
this.interceptor = interceptor;
ServerCertificateCustomValidationCallback = DangerousAcceptAnyServerCertificateValidator;
SslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
interceptor?.Intercept(request);
if (directConnect)
{
var host = request.RequestUri?.DnsSafeHost;
var isSslSession = request.RequestUri?.ToString().StartsWith("https://");
request.RequestUri = new Uri($"{(isSslSession is true ? "https://" : "http://")}{Dns.GetSourceIpAddresses()[0]}{request.RequestUri?.PathAndQuery}");
request.Headers.Host = host;
}
return await base.SendAsync(request, cancellationToken);
}
} where public interface IHttpRequestInterceptor
{
void Intercept(HttpRequestMessage message);
} and I need to prevent the server name to be the same as the Host header(in other words, the real hostname) |
Can you give any info on what scenarios you would use this in? This isn't clear to me from your example. |
I use this: string? SocketsHttpHandler.SslOptions.TargetHost |
Well, this scenario is rather special, I need to access IP directly to bypass the firewall (which will sniff the connection through both URI host and server name) but on the other hand, I also need to specify the Host header to tell the server which host I‘m trying to access, and I don't want to let the hostname appears in the server name because its sent by cleartext, which will make it too vulnerable to be sniffed by the firewall, and that's what I'm trying to prevent |
seems that it's only used by certificate validation according to the documentation, and my requirement is to find a way to set the server name during client hello |
I've never really checked this, but I believe it successfully influences the SNI in my case. It is also noticed in the source code of the Runtime accordingly: runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs Lines 270 to 271 in fdc6472
Or runtime/src/libraries/Common/src/Interop/Unix/System.Security.Cryptography.Native/Interop.OpenSsl.cs Lines 196 to 199 in f93c0e6
which calls OpenSSL's
|
Through runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/SocketsHttpHandler.cs Lines 453 to 471 in fdc6472
and the requests are processed by HttpConnectionPoolManager::SendAsync in HttpAuthenticatedConnectonHandler :Lines 18 to 21 in fdc6472
which invokes HttpConnectionPoolManager::SendAsyncCore Lines 360 to 366 in fdc6472
and step into its body, it is clearly that HttpConnectionPoolManager::SendAsyncCore calls HttpConnectionPoolManager::GetConnectionKey to get a HttpConnectionKey and pass it to a HttpConnectionPool , then invokes HttpConnectionPool::SendAsync :Lines 316 to 352 in fdc6472
It is noticed that the key.SslHostName is passed into HttpConnectionPool::ctor , and if we look furture into GetConnectionKey , there is a snippet about how to acquire the SslHostName :Lines 265 to 278 in fdc6472
which precisely implies what was aforementioned "it uses either the host of the request URI, or your specified Host header", we can find its application here: runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs Lines 213 to 249 in fdc6472
HttpConnectionPool constructs several SslOptions using our sslHostName and ConstructSslOptions , and the later one is what your example points to:runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs Lines 264 to 288 in fdc6472
According to my tracking steps above, I think change SslOptions.TargetHost won't work because no matter what value you set to it, it will always be modified at last in ConstructSslOptions unless you can intercept the request after ConstructSslOptions 's invocation, btw, I'd actually prefer to "turn off server name completely during this request"
|
Yup, indeed. The |
I would say that this could be a bug. Because Accordingly there should be the distinction: SocketsHttpHandler.SslOptions.TargetHost = null; // Like now, from Host-Header or from Url-Host
SocketsHttpHandler.SslOptions.TargetHost = String.Empty; // Disable SNI
SocketsHttpHandler.SslOptions.TargetHost = "SomeDnsOrIpEndPoint"; // Set the SslStream targetHost to SocketsHttpHandler.SslOptions.TargetHost |
It seems like they just completely prevented this in higher abstraction layers such as |
I think the Dotnet team just forgot to include it here: Lines 265 to 278 in fdc6472
|
Triage: @wfurt to look at it and provide recommendation. We may need more info on the use case. |
The use case is non-standard that the server refuses SNI. This can be useful in some internal network. |
The current workaround is rather more unsafe, I have to hook into runtime libraries and modify the runtime behaviors such as return value. IMO this should be considered as a design fault |
I think the SocketHttpHandler was never intended for tricks like this. This is conceptually similar to #29149 where once in a while somebody wants to do something special. The caveat here is that the SslOptions is set on handler and the TargetHost needs to be set for each server. So you would effectively need to create new handler for each site you need to visit - pattern we try to discourage when possible as that is very inefficient . Or at least serialize access if you choose to mutate the options. That may not be concern for the special needs. Conceptually, this can be also fixed with #46849 / #42455. (so as #29149) If you are desperate @dylech30th , for 5.0 you can construct `http://a.b.c.d/' URL and use ConnectCallback to return SslStream. That would avoid all the TLS logic in HttpClient and give you complete control. |
@wfurt Thanks for your suggestion. I've built a working sample! class DirectConnectHandler : HttpMessageHandler
{
private readonly HttpMessageInvoker _handler = new(new SocketsHttpHandler
{
ConnectCallback = async (context, ct) =>
{
var ip = CustomizeIP(context.DnsEndPoint.Host); // Get IP here
var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
await socket.ConnectAsync(IPAddress.Parse(ip), 443, cancellationToken: ct);
var networkStream = new NetworkStream(socket, true);
var sslStream = new SslStream(networkStream, false,
delegate { return true; });
await sslStream.AuthenticateAsClientAsync(""); // whatever you want, "" means no SNI
return sslStream;
}
});
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_handler.Dispose();
}
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
string? newUri = request.RequestUri!.ToString().Replace("https", "http", StringComparison.Ordinal);
request.RequestUri = new(newUri);
return _handler.SendAsync(request, cancellationToken);
}
} |
good to know @huoyaoyuan. #46849 is still coming to 6.0 but now you don't have to wait for it :) It may give you more elegant solution at some point. |
Triage: There is workaround and #42455 should solve this in 6.0. Closing this one as answered. |
Since the
WinHttpHandler
has been abandoned in .NET 5(at least I cannot findWinHttpHandler
anymore inHttpClientHandler
's source code),theSocketsHttpHandler
seems becomes the only possible choice, but it has a default behavior that I cannot set the server name directly or disable it, it uses either the host of the request URI, or your specified Host header, my requirement is, I want to keep the URI host as server name (or use custom one) while I can specify the Host headerThe text was updated successfully, but these errors were encountered: