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

HttpClient specify interface to bind to / make requests from #23267

Closed
narciero opened this issue Aug 20, 2017 · 70 comments
Closed

HttpClient specify interface to bind to / make requests from #23267

narciero opened this issue Aug 20, 2017 · 70 comments
Labels
area-System.Net.Http enhancement Product code improvement that does NOT require public API changes/additions
Milestone

Comments

@narciero
Copy link

On a machine with multiple NICs, how can i specify (if possible) which interface IP to use for making requests?

I am sometimes on a server where the default management interface is not connected to the internet, however there are multiple other NICs that are internet-facing that I would like to be able to use.

With .NET Framework + ServicePointManager I believe you could use BindIPEndPoint.

@davidsh
Copy link
Contributor

davidsh commented Aug 22, 2017

BindIPEndPoint delegate was never part of HttpClient. But it was part of HttpWebRequest on .NET Framework.

This is not supported on .NET Core.

@narciero
Copy link
Author

Are there any plans to support a multi network card scenario in .NET Core?

On a windows machine with multiple NICs how does HttpClient choose which interface to use?

@davidsh
Copy link
Contributor

davidsh commented Aug 22, 2017

cc: @stephentoub @karelz

@karelz
Copy link
Member

karelz commented Aug 22, 2017

That sounds like reasonable API request for HttpClient. Did we have any plans/idea how to eventually expose such functionality on HttpClient?

cc @geoffkizer

@PeterSmithRedmond
Copy link

Can I ask why you can't just let the system pick the correct network card? What's your network topology such that the wrong NIC might ever be picked?

If the system is confused enough to send regular traffic on the management NIC, having your app pick the management NIC is the least of your worries :-)

@narciero
Copy link
Author

narciero commented Sep 5, 2017

@PeterSmithRedmond It is common (and expected) to be able to control which network interface to bind to at the application level.

A machine may have multiple NICs all configured for standard traffic in addition to the management NIC. For example, consider a scenario where there are 3 NICs labeled A - C (+ mgmt card). Lets say I would like to make HTTP requests on interface "C", subscribe to multicast traffic on interface "B", and setup TCP listener on interface "A".

This functionality is already available for TcpClient/TcpListener and UdpClient which handles the 2 non-HTTP cases above, making it easy if I decide to rearrange my setup and move my tcp listener to interface B, etc. It would be great if HttpClient could support this type of configuration which is quite common in enterprise scenarios.

@narciero
Copy link
Author

narciero commented Oct 6, 2017

as a follow up to @karelz post, are there any plans to eventually implement this? i am happy to help out, thanks.

@narciero
Copy link
Author

narciero commented Oct 9, 2017

It looks like ConnectHelper is where the necessary code should be to make this happen. In the try-catch after creating the socket (right before the call to ConnectAsync you would just need socket.Bind(localEP):

public static async ValueTask<Stream> ConnectAsync(string host, int port, EndPoint localEP = null)
{
   var socket = new Socket(SocketType.Stream, ProtocolType.Tcp) { NoDelay = true };
   try
   {
      // **optionally bind to local endpoint**
      if (localEP != null)
         socket.Bind(localEP);

      // TODO dotnet/runtime#23148: cancellation support?
      await (IPAddress.TryParse(host, out IPAddress address) ?
         socket.ConnectAsync(address, port) :
         socket.ConnectAsync(host, port)).ConfigureAwait(false);
   }
   catch (SocketException se)
   {
      socket.Dispose();
      throw new HttpRequestException(se.Message, se);
   }

   return new NetworkStream(socket, ownsSocket: true);
}

I think the main question is how do we want the localEP argument to bubble up the stack so it can easily be configured on a per client basis?

Happy to open a pull request for this if you guys are open to including this in the near future as its a pretty small change (and already supported by Socket).

@PeterSmithRedmond
Copy link

When you document this, please remind people that there's a bunch of traffic that might happen that won't go over that interface. For example, DNS lookups and getting certificates and revocation lists won't be compelled to use the IP address.

Can you go into more detail on the rationale? you say that people can set up different cards for different traffic -- is this a best practice for data centers? Normally, the best practice is to let the OS decide. What is the benefit of deliberately splitting up the traffic this way?

@wfurt
Copy link
Member

wfurt commented Dec 8, 2017

What is your use case @narciero aside from desire to control? In many cases you can set up routing table to have desired behavior. It is IP's job to decide packet routing, not HTTP. BTW I think the comparison to Listener does not make much sense to me. In that case you trying to create service which will wait for requests. Responses will be routed according to routing table AFAIK.

@felixg
Copy link

felixg commented Jan 9, 2018

Many APIs, especially with high security requirements like payments, require IP white-listing. More common scenario is Facebook recommending to maintain Server IP Whitelist to protect Graph API calls. Routing cannot solve this issue because the target IPs aren't constant nor completely known in advance.

@taualex
Copy link

taualex commented Jul 5, 2018

I'd like to voice my support for this as well. In our scenario, we have a set of dynamic VMs (one active, a few on standby), and one of the VMs is set (elected) as a current work node with a Global IP bound to it. The Global IP can be re-assigned to another VM if the current is unavailable (a fail-over event).
This creates a situation when a VM has 2 public IPs. We need to connect to a remote web server (say, WebAPI 3rd party server) which has our Global IP whitelisted. If we can bind our Global IP as an outgoing IP for the HttpClient, it will solve the issue.

@simonthum
Copy link

simonthum commented Sep 15, 2018

Another voice of support: On our IOT use cases, we often need to select the network (e.g. WLAN vs. Broadband) based on specific criteria that may hold per request (such as expected download volume). This is crucial to our ability to connect.

The Application is on a mobile device, we often do not even know much about the target network(s) and windows doesn't either.

We don't have concrete plans for .net core in this setting but this is a blocker.

@karelz
Copy link
Member

karelz commented Sep 15, 2018

@simonthum that is interesting scenario, thanks for bringing it to our attention.

Ask for everyone: Please upvote top comment if it is a feature you need - it will help us with prioritization.

@stephentoub
Copy link
Member

stephentoub commented Sep 15, 2018

Personally I'd prefer to see this functionality supported via allowing a callback to either create or configure the Socket, e.g. along the lines of

public Func<Socket> CreateSocketFunction { get; set; }

or

public Action<Socket> ConfigureSocketFunction { get; set; }

Then a client could handle this scenario by calling Bind on the Socket, but other configuration would be supported as well, via either what Socket provides or even platform-specific via P/Invokes.

@simonthum
Copy link

simonthum commented Sep 15, 2018

@stephentoub Yes Func<Socket> is better than Func<IPAddress>. In our IOT setting we sometimes connect over several networks to check which network is able to connect to the target. The first answer wins, the rest disconnects.

It is a fall-back, but even then in a Func<IPAddress> I would be forced to turn down an established connection and hope the caller will re-establish it. This makes an already ugly fallback strategy considerably more ugly.

@geoffkizer
Copy link
Contributor

I think it would need to be something like:

public Func<Socket, string, string, int> ConnectFunction { get; set; }

The arguments here being scheme, hostname, and port.

Or even:

public Func<Stream, string, string, int> ConnectFunction { get; set; }

Which would allow you to return an arbitrary stream.

There are a few details that would need to be worked out:
(1) How does this interact with proxy lookup?
(2) If we allow you to return an arbitrary stream, then how does this interact with SSL handling? I'm guessing that the specified SSL options are ignored and you can just use whatever options you want. Also, we need to retrieve stuff like ALPN result from the SslStream in order to do HTTP2, but I suppose we could just test if the resulting stream is SslStream and if so, retrieve that info.

@siberianguy
Copy link

Is there any progress with this issue? We're stuck by not being able to explicitly specify an ip address for a query. We're considering using a wrapper for a C++ library as a temporary solution but it doesn't sound super exciting...

@simonthum
Copy link

It sounds like a 100% solution would be to factor out the connection setup such that individual steps can be performed by the user selectively. I could open the socket, a stream, decide on reuse (pipelining, http/2), specify a binding address, a proxy, TLS certs etc. but not in every combination. Like a connection management interface with several default implementations to support the desired degree of control.

That would be complex, though certainly possible and useful beyond HttpClient.

I don't like the (existing) Func<IPAddress, xxx> based approach much, but it delivers 80% of the value for 10% of complexity.

@siberianguy
Copy link

Sorry guys for a likely stupid question but I'm a little lost here. On the one hand, this issue says HttpClient doesn't support specifying an explicit ip address but then I see issues like this one (restsharp/RestSharp#1162) that shows seemingly that you can change an ip address

@simonthum
Copy link

@siberianguy You can using HttpWebRequest which (I presume) is not in .net core and quite ancient.

@stephentoub
Copy link
Member

stephentoub commented Sep 19, 2018

I think it would need to be something like

Sure, I was simply highlighting using a Func that returns a Socket or an Action that configures one. We could figure out what arguments to pass in. I could imagine you might want more than just the scheme/host/port in some situations, as well.

Which would allow you to return an arbitrary stream.

That's a whole different ballgame. Just providing a delegate to a configure a socket, or even to create one, leaves the whole connect mechanism up to the SocketsHttpHandler implementation, and as it implemented the connect, it can make assumptions based on it. Elevating that to a delegate that creates the Stream is potentially more powerful, yes, but also is more intrusive, forces our hands on various SocketsHttpHandler implementation details (we may no longer have access to the underlying NetworkStream, we can no longer assume SslStream in cases where we cast to it, etc.), etc., and causes the questions you answer. I've also not seen a use case that would demand this level of control.

In contrast, there are known uses for being able to at least configure the socket before the connection attempt is made, and even creating the socket itself (e.g. whether to create it only for IPv4 or IPv6). I'm sure we could come up with use cases for a custom stream, I'm just not yet convinced its worth it.

Whatever we do, though, we should probably do it in conjunction with https://github.com/dotnet/corefx/issues/27949, or at least factoring that in, as that could also impact how the socket is created.

@wfurt
Copy link
Member

wfurt commented Sep 19, 2018

It may also help with cases like dotnet/corefx#31951 when someone may want more aggressive retries.

@geoffkizer
Copy link
Contributor

@stephentoub Yes, I agree with your points. I was mostly just tossing out some ideas above.

there are known uses for being able to at least configure the socket before the connection attempt is made, and even creating the socket itself

Agreed. To answer my own questions above, I don't think this should affect proxy behavior or SSL. It should just affect how connections are created.

Re configure socket vs create socket, it seems to me there are compelling cases for creating the socket, so I lean toward that model. Basically, something like a Func<Socket, DnsEndpoint>.

I'm sure we could come up with use cases for a custom stream, I'm just not yet convinced its worth it.

I basically agree. We should lean toward being conservative here, i.e. Socket. That said, most of the code internally is Stream based anyway because it needs to run over SslStream as well as NetworkStream. But I could go either way on this.

@siberianguy
Copy link

Is there any hope with this issue? Honestly, it's beyond my understanding how we've got so far with .net core without having a key feature for many enterprise solutions

@karelz
Copy link
Member

karelz commented Jan 8, 2019

Why do you think it impacts "many" enterprise solutions? I see only 8 upvotes on the top issue, also we rarely hear about it from other channels.
The bottom line is that it is on our backlog, but not too high up. We have larger things to finish first: HTTP/2, proxy support for enterprise scenarios, etc.
I hope we will be able to get to issues like this post 3.0.

@siberianguy
Copy link

There's a long list of use cases which require choosing an ip address for a request. And it's not only complicated enterprise solutions. Here's our scenario. We're working with crypto exchanges and we have to be very careful with the amount of requests we're making from one ip address, so it's vital to be able to send requests under all the ip addresses attached to a server. We're struggling with the lack of this feature to say the least. May be I'm missing something but in my opinion this feature is something fundamental, so I'm confused why it's considered to be optional.

@karelz
Copy link
Member

karelz commented Jan 8, 2019

@siberianguy almost every bug/feature can be viewed as fundamental/critical in certain scenarios. The key is "How many customers does it impact?"
This is a feature which impacts narrower set of customers. We will eventually get to it, it is just not priority right now as we're working on features/bugs which impact larger number of customers. Does it make sense?

@siberianguy
Copy link

@karelz I definitely see where you're coming from but I believe the analysis should be more in-depth than "how many people are influenced by this". I'm pretty sure if we sit together and go through the changes list for 3.0 we will find a bunch of really optional things. Yes, they likely cover wider audiences but they're minor improvements that don't influence those wider audiences' life that much. Here we're talking about a fundamental network-related feature which makes it impossible to implement a significant number of use cases using .net core

@seagulledge
Copy link

I have another example:
If the 'default' network IP address is NULL routed due to a DDoS mitigation.
A web site can still be accessed via other another IP address by clients, but if the web service needs to make a server side call using HttpClient to an external resource, the call will fail to connect, since its source IP will be the NULL routed default IP on the server.

@bonesoul
Copy link

@tundeanderson same issue here, were you able to find a solution?

@programcsharp
Copy link

Another vote here. And please backport to full framework HttpClient to ward off reflection based solutions like: https://stackoverflow.com/questions/39689858/how-to-use-httpclient-to-send-a-request-from-a-specific-ip-address-c-sharp

@tundeanderson
Copy link

@tundeanderson same issue here, were you able to find a solution?

Unfortunately not

@stefan-wenig
Copy link

stefan-wenig commented Aug 7, 2019

here's an important security-related scenario: every application that allows user-provided webhooks is exposed to the risk of a hacker trying to access local ressources as in http://localhost/... - and that's harder to prevent that it looks at a first glance: see http://blog.fanout.io/2014/01/27/how-to-safely-invoke-webhooks/ and https://news.ycombinator.com/item?id=7139176

using dedicated NICs that are only routed to the internet would be the easiest way to prevent this. if only I could specify a binding.

+1 for the notion that this is just a must-have for any HTTP client. we should not have to argue our case.

@xLabel
Copy link

xLabel commented Sep 16, 2019

+1

@karelz
Copy link
Member

karelz commented Oct 10, 2019

Triage: We plan to address this as part of #28721 ... keeping it open due to high popularity of this issue.

@msftgits msftgits transferred this issue from dotnet/corefx Jan 31, 2020
@msftgits msftgits added this to the 5.0 milestone Jan 31, 2020
@Lewington-pitsos
Copy link

Lewington-pitsos commented Apr 7, 2020

@tundeanderson It not available for .NET Core only. In the classic framework you can use BindIPEndPoint

Or you can switch to a lower level and use other primitives instead.

Would you be able to specify which "lower level" primitives you were thinking of? I'm not sure where to start looking for alternatives to HttpClient

EDIT:

I ended up using the Socket class and it worked.

@georgiosd
Copy link

Amazing that in two years, a feature with all these comments and legit requests hasn't been actioned on.

Is this even going to be in 5.0 @karelz ?

@bonesoul
Copy link

bonesoul commented May 6, 2020

@georgiosd also the request is about a common functionality where you expect it have already in by default in any framework / language.

@georgiosd
Copy link

georgiosd commented May 6, 2020

@bonesoul it's a long thread already with arguments for either side so given that hasn't worked out too well I don't think there's a point in continuing.

I get @karelz need to prioritize based on what the majority of users "need" but frankly I think it's bad for business to piss any one group of people off for such a long time, no matter how small the group.

I'm also a bit pissed personally because I realized that even though HttpWebRequest exposes this in 3.1, it doesn't work, it seems to ignore the setting. Same code in net47 works. So basically I introduced a bug in my client's software by upgrading and I didn't even know about it until now (months later), when I was checking an unrelated issue.

@karelz
Copy link
Member

karelz commented May 6, 2020

Things often are not as simple as they seem. This is part of larger proposal (#28721) which morphed into more general solution in #1793 (which is in active development since November - experiments, architecture review rounds, things to consider and unify with ASP.NET models, compromises on the way). So, yes, it will be part of 5.0 unless something super horrible happens (the API is scheduled for larger API review next week).

I am sorry that you've ran into HttpWebRequest limitations -- those APIs are compat-only to ease migration from .NET Framework, quite a few of their properties are no-ops. Starting with 5.0 it seems we will have finally built-in mechanism to convey this information to developers in a reasonable way via Roslyn analyzers (tracked in #33125).

@shaggygi
Copy link
Contributor

shaggygi commented May 6, 2020

@karelz

So, yes, it will be part of 5.0 unless something super horrible happens (the API is scheduled for larger API review next week).

By chance is this review going to be recorded like Immo usually does?

BTW... looking forward to your ZBB as 5.0 release nears. 😄

@karelz
Copy link
Member

karelz commented May 6, 2020

Yes, it should be part of normal streamed/recorded API review (cc @scalablecory).

@scalablecory
Copy link
Contributor

scalablecory commented May 6, 2020

@shaggygi please feel free to review #1793 and participate in API review via YouTube chat. Barring last minute rescheduling, we will be reviewing this on 5/14.

@GuerrillaCoder
Copy link

Is this feature scheduled?

@stephentoub
Copy link
Member

#1793 is planned for .NET 5 and should address this.

@scalablecory
Copy link
Contributor

Closing this -- resolved by #1793.

@scalablecory
Copy link
Contributor

This has been resolved via the API added here in .NET 5: #41949

@Pro100AlexHell
Copy link

I have simple task - detect remote IP address to which http client is connected. I don't want to DNS resolve, but need exactly remote IP.
I didn't understand how to detect remote IP in net core 3.1.
Also I have no time for reading all of topics regarding breaking changes and new API.

I have found solution for .net - https://stackoverflow.com/questions/6655713/how-to-get-ip-address-of-the-server-that-httpwebrequest-connected-to but it doesn't work as it is for dot net, but not core.

Answer please, my simple question.
I can use WebRequest or HttpClient or anything you suggest.

@bonesoul
Copy link

bonesoul commented Nov 9, 2020

@scalablecory is there a sample for usage?

@stephentoub
Copy link
Member

is there a sample for usage?

socketsHandler.ConnectCallback = async (context, token) =>
{
Socket s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
s.Bind(new IPEndPoint(IPAddress.Loopback, 0));
await s.ConnectAsync(context.DnsEndPoint, token);
s.NoDelay = true;
return new NetworkStream(s, ownsSocket: true);
};

@bonesoul
Copy link

for anyone that may be interested a created a sample repo as an example: https://github.com/bonesoul/dotnet_5_httpclient_rest_bind

@geoffkizer
Copy link
Contributor

@bonesoul Nice, thanks. BTW you can just create SocketsHttpHandler directly, instead of creating HttpClientHandler and then doing the reflection stuff. The tests do the latter because that's how some of the underlying test infra code is set up.

@ghost ghost locked as resolved and limited conversation to collaborators Dec 20, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
area-System.Net.Http enhancement Product code improvement that does NOT require public API changes/additions
Projects
None yet
Development

No branches or pull requests