dotnet / runtime

.NET is a cross-platform runtime for cloud, mobile, desktop, and IoT apps.
https://docs.microsoft.com/dotnet/core/
MIT License
14.98k stars 4.66k forks source link

How to use proxy on Windows and Linux when PAC file is used #106933

Open SlavaC1 opened 3 weeks ago

SlavaC1 commented 3 weeks ago

We are using .NET 8 in our project that should run on Windows and Linux. On some of our systems proxy is defined as a specific URL and on some as a PAC script. We want to be able to get the correct proxy regardless the method it is defined on this specific system.

I understood that at least on Windows we can use WebRequest.GetSystemWebProxy() to get the correct proxy address even it is defined in PAC file, but this API is deprecated and HttpClient is advised to be used instead, but I couldn't find equivalent API that achieves the same result. Also if I understood correctly, WebRequest.GetSystemWebProxy() does not support PAC files on Linux.

Please advise what is the correct way to get the proxy setting on both Windows and Linux systems regardless if it's PAC file or not.

huoyaoyuan commented 3 weeks ago

WebRequest.GetSystemWebProxy() delegates to HttpClient.DefaultProxy. You can use this static property instead.

SlavaC1 commented 3 weeks ago

@huoyaoyuan does it work correctly on both Windows and Linux and also with PAC files?

huoyaoyuan commented 3 weeks ago

No. PAC files are processed with WinHttp on Windows and CFProxyAutoConfigurationResultCallback on MacOS. On Linux only the HTTP(S)_PROXY environment variables are supported and I can't find conventional way to use PAC with it.

SlavaC1 commented 3 weeks ago

Thanks a lot for your reply

wfurt commented 3 weeks ago

PAC is evil IMHO as it requires executing script. That may work well for browsers but it sucks for everything else. as @huoyaoyuan correctly stated, on Linux the only option is API call to set it explicitly or environment variables

SlavaC1 commented 3 weeks ago

I'm trying to use HttpClient to use default proxy as defined in Windows, meaning without any parameters. I'm trying to connect to an address that requires proxy which is defined in Windows. When connecting from browser I have no problems. When connecting from my sample app from HttpClient I get:

System.AggregateException: One or more errors occurred. (A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (pressqa.printopt.org:443)) ---> System.Net.Http.HttpRequestException: A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. (pressqa.printopt.org:443) ---> System.Net.Sockets.SocketException (10060): A connection attempt failed because the connected party did not properly respond after a period of time, or established connection failed because connected host has failed to respond. at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ThrowException(SocketError error, CancellationToken cancellationToken) at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.System.Threading.Tasks.Sources.IValueTaskSource.GetResult(Int16 token) at System.Net.Sockets.Socket.<ConnectAsync>g__WaitForConnectWithCancellation|285_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken) --- End of inner exception stack trace --- at System.Net.Http.HttpConnectionPool.ConnectToTcpHostAsync(String host, Int32 port, HttpRequestMessage initialRequest, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.ConnectAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.CreateHttp11ConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.AddHttp11ConnectionAsync(QueueItem queueItem) at System.Threading.Tasks.TaskCompletionSourceWithCancellation1.WaitWithCancellationAsync(CancellationToken cancellationToken) at System.Net.Http.HttpConnectionPool.SendWithVersionDetectionAndRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken) at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) at System.Net.Http.HttpClient.g__Core|83_0(HttpRequestMessage request, HttpCompletionOption completionOption, CancellationTokenSource cts, Boolean disposeCts, CancellationTokenSource pendingRequestsCts, CancellationToken originalCancellationToken) --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task1.GetResultCore(Boolean waitCompletionNotification) at ProxyProvider.Program.Main(String[] args) in D:\Personal\CodeTests\dotnet\Proxy\ProxyProvider\Program.cs:line 40

Any ideas?

SlavaC1 commented 3 weeks ago

I did some more tests and it seems like on Linux HttpClient picks up the default proxy correctly, but not on Windows. I used both HttpClientHandler and SocketsHttpHandler and I see the same behavior.

wfurt commented 3 weeks ago

What is your code and configuration @SlavaC1? If the proxy is configured on Windows in setting, there should not be anything you need to do.

SlavaC1 commented 3 weeks ago

I have Windows and Linux systems both configured with the same proxy address. When running HttpClient.DefaultProxy.GetProxy(testUri) on both, I get the same correct proxy.

Now I'm trying to connect to some address behind the proxy like this:

using var client = new HttpClient();
var res = client.GetAsync(new Uri("https://some.address.com", UriKind.Absolute)).Result;

On Linux I connect without problems, but on Windows I get the timeout.

Following "workaround" works correctly on both systems:

using var handler = new SocketsHttpHandler();
handler.Proxy = new WebProxy(HttpClient.DefaultProxy.GetProxy(new Uri("http://printos.com", UriKind.RelativeOrAbsolute)));

using var client = new HttpClient(handler, true);

var res = client.GetAsync(new Uri("https://some.address.com", UriKind.Absolute)).Result;
res.EnsureSuccessStatusCode();
wfurt commented 3 weeks ago

If the proxy is configured in system it should just work e.g. there is no need to set it explicitly.

SlavaC1 commented 3 weeks ago

@wfurt this is also what I understand, but unfortunately the results are different. Could be that I'm missing some configuration? Or the proxy is defined differently on Windows somehow?

wfurt commented 3 weeks ago

what do you see in Settings? You can also set the environment for testing. That would take precedence over Windows setting AFAIK. As @huoyaoyuan mentioned, we get the actual info by calling windows functions. You can inspect the created object in debugger. Perhaps there will be some clue.

dotnet-policy-service[bot] commented 2 weeks ago

This issue has been marked needs-author-action and may be missing some important information.

dotnet-policy-service[bot] commented 3 days ago

This issue has been automatically marked no-recent-activity because it has not had any activity for 14 days. It will be closed if no further activity occurs within 14 more days. Any new comment (by anyone, not necessarily the author) will remove no-recent-activity.