-
Notifications
You must be signed in to change notification settings - Fork 4.9k
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
Option to have more control when resolving endpoint IP address in SocketsHttpHandler #25401
Comments
This is essentially a hook for providing custom name resolution instead of calling Dns.GetHostEntry. Right? As such, the callback signature should be similar to the Dns.GetHostEntry, which just takes a string and returns an IPHostEntry (which includes an array of IPAddresses). And it should probably be called something very similar, like GetHostEntryCallback. Note that we don't resolve DNS names per-request; we only do it when we don't have an existing connection in the pool that we can use. So if you're expecting this callback to happen on every request, you're going to be disappointed. The naming/usage should hopefully make this clear. Also note that SocketsHttpHandler does not itself call Dns.GetHostEntry; we just pass the DNS name to Socket.ConnectAsync and let it do its thing (which includes retrying on connection failure when there are multiple IPAddresses specified). So this may need to get plumbed all the way down to Socket in order to be implemented properly. |
Hello. As currently implemented on https://github.com/AppBeat/corefx, if this callback is set, this should be resolved before getting connection from the pool (at HttpConnectionPoolManager stage in new "GetConnectionKeyAsync" method). Later, when ConnectHelper.ConnectAsync is called, host parameter will always be resolved IP address at this point if SocketsHttpHandler.ResolveEndpointAsync callback is used: In theory my current experimental implementation should work without going too deep into Sockets or doing any drastic changes. Please check my changes on https://github.com/AppBeat/corefx/commits/master Kind regards, |
So you're saying you want this callback to be per-request? I don't think this is a good idea. It means we're potentially doing DNS lookup per-request, and DNS lookup is not necessarily cheap. That's why we only do it when we need to establish a new connection. It's also a much bigger change to a critical code path, which scares me. A simple hook for DNS resolution is straightforward and self-contained. |
It is up to implementer of ResolveEndpointAsync to take care of efficiency. We are giving him/her 100% freedom :) If SocketsHttpHandler.ResolveEndpointAsync is not set by user, we should have same experience / performance as today (same resolving logic will be used as today). Will this feature be useful for some user, will it be ever used in real life? This I don't know :) |
Let me put it this way. Is there some reason this needs to be per-request? |
If connection pool is disposed as soon as SocketsHttpHandler is disposed (which I think it does), then IP address could be internally cached and callback wouldn't have to be called for each request (for the lifetime of SocketsHttpHandler instance). If this is not the case then we don't have full control. In case of internal address caching it would make sense to change "ResolveEndpointAsync" signature and remove HttpRequestMessage to reflect this (as you suggested previously). We would then have something like this:
|
It is.
Which brings me back to my original feedback:
I'll modify this slightly: It should look like GetHostAddressesAsync. I don't think there's any reason we need an IPHostEntry here, just the IPAddress[] should be fine. |
IPAddress without port should also be OK. But why IPAddress[] array would be returned instead of just one IPAddress? Which IPAddress would then be used by SocketsHttpHandler and underlying infrastructure? |
I do wonder whether we actually want to restrict this to IPAddress. What if someone wanted to, for example, target an UnixDomainSocketEndPoint? |
@stephentoub I agree. Probably more general "EndPoint" should be in callback method signature. |
Update: IPAddress was currently done on my fork because it was much easier to implement and plug-in into existing implementation (into "GetConnectionKey" logic). |
Maybe users should vote for this feature if they find it relevant. I wouldn't change current API / do drastic changes if current solution fits "99.99%" of users. |
Looking at the list of use-cases:
This would be hard, since there is no API to do a DNS request against a specific DNS server.
The system/corefx should do a good job here. It would be hard for the user to provide a better implementation.
This seems a valid use-case. When setting up some services locally, it is not straight forward to make the system resolve hostnames to those addresses. // Adds a fixed host lookup.
Dns.AddHostEntry(string, IPHostEntry) @AppBeat wdyt? |
I had something like this in mind: socketsHttpHandlerInstance.ResolveEndpointAsync = async delegate (HttpRequestMessage request, string host, int port, CancellationToken cancellationToken)
{
return ResolveIPv6FromGoogleDnsAsync(host, port); //method implemented by user
};
private static async Task<IPEndPoint> ResolveIPv6FromGoogleDnsAsync(string host, int port)
{
//this is just quick example, user could also cache results...
using (var client = new HttpClient())
{
var serializer = new JsonSerializer();
using (var sr = new StreamReader(await client.GetStreamAsync(new Uri($"https://dns.google.com/resolve?type=AAAA&name={WebUtility.UrlEncode(host)}")).ConfigureAwait(false)))
using (var jsonTextReader = new JsonTextReader(sr))
{
var json = serializer.Deserialize(jsonTextReader) as JObject;
if (json == null)
{
return null;
}
var dnsAnswer = json.Property("Answer")?.Value as JArray;
if (dnsAnswer == null)
{
return null;
}
foreach (var dnsAnswerEntry in dnsAnswer)
{
var data = dnsAnswerEntry.Value<string>("data");
var type = Uri.CheckHostName(data);
if (type == UriHostNameType.IPv6)
{
if (IPAddress.TryParse(data, out IPAddress ipv6Address))
{
return new IPEndPoint(ipv6Address, port);
}
}
}
return null; //or throw Exception if user requires IPv6 to be resolved - logic is 100% up to user
}
}
}
// Adds a fixed host lookup.
Dns.AddHostEntry(string, IPHostEntry) I think this could be generally useful for a lot of scenarios. This call would of course affect just running process so there would be no side effects. Now you would have to edit %SystemRoot%\System32\drivers\etc\hosts file for which you must have administrative privileges (and affects entire machine). |
It will be solved by dotnet/corefx#35404 |
Duplicate of dotnet/corefx#35404 |
This has been resolved via the API added here in .NET 5: #41949 |
I think it may be useful in some cases to have more control over endpoint address resolving.
This would be new optional feature in SocketsHttpHandler implementation. Here is my initial idea.
Proposed API
where delegate inputs are:
and delegate output is:
Similar Func<> pattern is used in WinHttpHandler.ServerCertificateValidationCallback
Example usage
Main goals
Possible use cases
Prototype implementation: https://github.com/AppBeat/corefx
Very early PR: dotnet/corefx#27937
Kind regards,
Vladimir
The text was updated successfully, but these errors were encountered: