I have an ASP.NET Core 3.1 application running in Azure App Service. It makes calls to Elasticsearch running on an Ubuntu Server 16.04 LTS Azure Virtual Machine, connected to the App Service via an Azure VNet. When I upgraded the application to target net5.0
, all of my calls to Elasticsearch started failing with the following exception:
System.Net.Http.HttpRequestException: An attempt was made to access a socket in a way forbidden by its access permissions. (10.1.0.5:9200)
---> System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
Through trial and error, I eliminated Elasticsearch from the equation and reproduced the issue with simple HTTP requests via HttpClient
:
public async Task<IActionResult> VNetRequest()
{
string? body;
try
{
using var requestMsg = new HttpRequestMessage(HttpMethod.Get, _testRequest.VNetEndpoint);
using var responseMsg = await _httpClient.SendAsync(requestMsg);
body = await responseMsg.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
body = ex.ToString();
}
return Content(body);
}
When targeting netcoreapp3.1
, this call will succeed and display some nginx boilerplate text.
When targeting net5.0
, this call will fail with the following exception:
System.Net.Http.HttpRequestException: An attempt was made to access a socket in a way forbidden by its access permissions. (10.1.0.4:80)
---> System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
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.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, 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.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at AppServiceVNetCalls.Controllers.HomeController.VNetRequest() in D:\home\site\repository\src\AppServiceVNetCalls\Controllers\HomeController.cs:line 34
When I run these tests locally, I don't get any failures; everything works fine.
At this point, I'm stuck, and I don't know how where to go next.
I have a simple ASP.NET Core application that demonstrates the issue. This repository has two branches, net31
and net5
:
net31
: This demonstrates that HttpClient
GET requests to an Azure VNet IP address succeed. It also demonstrates that requests to a non-Azure VNet IP address succeed. See: https://appservicevnettest31.azurewebsites.net/net5
: This demonstrates that HttpClient
GET requests to an Azure VNet IP address fail. It also demonstrates that requests to a non-Azure VNet IP address succeed. See: https://appservicevnettest50.azurewebsites.net/The architecture of my Azure reproduction is as follows:
Thank you.
ETA: Please let me know when you no longer need these VMs/App Services so that I can delete them. Thanks.
Tagging subscribers to this area: @dotnet/ncl I have an ASP.NET Core 3.1 application running in Azure App Service. It makes calls to Elasticsearch running on an Ubuntu Server 16.04 LTS Azure Virtual Machine, connected to the App Service via an Azure VNet. When I upgraded the application to target Through trial and error, I eliminated Elasticsearch from the equation and reproduced the issue with simple HTTP requests via When targeting When targeting When I run these tests locally, I don't get any failures; everything works fine. At this point, I'm stuck, and I don't know how where to go next. I have a simple ASP.NET Core application that demonstrates the issue. This repository has two branches, The architecture of my Azure reproduction is as follows: Thank you.
See info in area-owners.md if you want to be subscribed.
Issue Details
Description:
net5.0
, all of my calls to Elasticsearch started failing with the following exception:System.Net.Http.HttpRequestException: An attempt was made to access a socket in a way forbidden by its access permissions. (10.1.0.5:9200)
---> System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
HttpClient
:public async Task<IActionResult> VNetRequest()
{
string? body;
try
{
using var requestMsg = new HttpRequestMessage(HttpMethod.Get, _testRequest.VNetEndpoint);
using var responseMsg = await _httpClient.SendAsync(requestMsg);
body = await responseMsg.Content.ReadAsStringAsync();
}
catch (Exception ex)
{
body = ex.ToString();
}
return Content(body);
}
netcoreapp3.1
, this call will succeed and display some nginx boilerplate text.net5.0
, this call will fail with the following exception:System.Net.Http.HttpRequestException: An attempt was made to access a socket in a way forbidden by its access permissions. (10.1.0.4:80)
---> System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
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.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, 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.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
at AppServiceVNetCalls.Controllers.HomeController.VNetRequest() in D:\home\site\repository\src\AppServiceVNetCalls\Controllers\HomeController.cs:line 34
Reproduction repository and Azure applications
net31
and net5
:
net31
: This demonstrates that HttpClient
GET requests to an Azure VNet IP address succeed. It also demonstrates that requests to a non-Azure VNet IP address succeed. See: https://appservicevnettest31.azurewebsites.net/net5
: This demonstrates that HttpClient
GET requests to an Azure VNet IP address fail. It also demonstrates that requests to a non-Azure VNet IP address succeed. See: https://appservicevnettest50.azurewebsites.net/
Author:
jonsagara
Assignees:
-
Labels:
area-System.Net.Http
, untriaged
Milestone:
-
Do you have SELinux enabled?
I don't believe so. I'm using Windows App Service, not Linux.
I have the same issue calling a service over vnet, this blocks the upgrade to net5.0 for us.
We have the same issue, it appears. Discovered a bizarre thing:
Azure App Service on a Windows service plan, running .net50
System.Net.Http.HttpRequestException: An attempt was made to access a socket in a way forbidden by its access permissions. (sommeurl:443)
---> System.Net.Sockets.SocketException (10013): An attempt was made to access a socket in a way forbidden by its access permissions.
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|283_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
at System.Net.Http.ConnectHelper.ConnectAsync(Func
3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(Func3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, 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.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(HttpRequestMessage request, Boolean async, Boolean doRequestAuth, CancellationToken cancellationToken)
at System.Net.Http.RedirectHandler.SendAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.DiagnosticsHandler.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at Microsoft.Extensions.Http.Logging.LoggingScopeHttpMessageHandler.SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
at System.Net.Http.HttpClient.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
We found a solution: our app service was connected to our vnet through the gateway subnet. Disconnecting it and reconnecting the app to our vnet through a new subnet solved the issue.
For a short while.
Disconnecting (but not reconnecting) solves the issue, apparently vnet integration is the problem with the HttpClient in .Net 5.0.
Could someone please explain why .Net Core 3.1 does work and .Net 5.0 doesn't?
@jonsagara and others facing this issue, any chance you can also raise a bug report on the Azure Portal feedback page (if doesn't exist yet):
https://feedback.azure.com/forums/223579-azure-portal/category/129061-bugs
At this moment it seems to us that the issue is more likely on their side, and they may be able to root cause it much faster since the investigation needs Azure infrastructural knowledge much more than anything else.
After opening it, can you share the link of the report so we can use it when contacting the Azure team?
Will do.
Did you see this:
https://github.com/MicrosoftDocs/azure-docs/issues/45991
I got a ticket with MS Support, and indeed the issue is that some client libraries don't force IPv4 (as TcpClient) as the virtual NIC installed on the App Services workers (for regional VNet integration) don't support IPv6.
If IPv6 is not supported, I would expect it would not be advertised via DNS. If both protocols families are available, TcpClient should keep trying all available entries.
Found a workaround: set the url to the ipv4 address, add a header with "Host: domain.name" to the request and .net 5.0 httpclient can connect to a host running on the vnet.
Just noted that this only works when using the public ip-address of the VM that I need to connect to, not the private one.
This issue is affecting me as well, and has halted our .NET 5 upgrade effort at work. Has anyone opened a support incident with Microsoft for this? Or is the bug report on the Azure Portal feedback page sufficient for triggering timely action on their end?
We are looking into this with the help of Azure App Services team, but it's too early to provide a plan or ETA. Will update as soon as we can.
According to our current (probably incomplete?) understanding, the root cause of the issue is that _App Service currently does not support dual-stack sockets with VNET integration_.
A workaround idea (specific to .NET 5) is to utilize ConnectCallback and enforce creating IPV4 sockets:
```C#
public async HttpClient CreateWorkaroundClient()
{
SocketsHttpHandler handler = new SocketsHttpHandler
{
ConnectCallback = IPv4ConnectAsync
};
return new HttpClient(handler);
static async ValueTask<Stream> IPv4ConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// By default, we create dual-mode sockets:
// Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
try
{
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
}
```
@jonsagara @davebronson @kwesseling and others watching this issue: can you try if this workaround helps?
The connection is done within a third party library, NEST (https://github.com/elastic/elasticsearch-net). I'll see if they have extension points, though.
ETA: I asked about customizing HttpClient creation in NEST:
https://discuss.elastic.co/t/nest-customize-creation-of-httpclient/256687
This is definitely a behavioral breaking change between 3.1 and 5.0, since we did not use Dual-Mode sockets before:
As far as I can understand, the problem is that it can it happen in a given environment that despite the fact that IPV6 is supported by the OS (Socket.SupportsIPv6
returns true), dual-stack sockets are disallowed. We can probably try to detect such cases in the default Connect logic of SocketsHttpHandler and fall back to IPV4 sockets if dual mode is not available. @scalablecory @geoffkizer thoughts?
Hello and thank you for the example code.
I can confirm it solves the problem for us.
```c#
public class DOClient
{
public HttpClient HC { get; }
public DOClient(IConfiguration config, HttpClient client)
{
var PersonalAccessToken = config["Tokens:AzureDevOps"];
var base64Token = Convert.ToBase64String(Encoding.ASCII.GetBytes($":{PersonalAccessToken}"));
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", base64Token);
client.DefaultRequestHeaders.Add("Accept", "application/json");
HC = client;
}
}
public static void ConfigureBindings(IServiceCollection services)
{
services.AddHttpClient<DOClient>()
.ConfigurePrimaryHttpMessageHandler(() => new SocketsHttpHandler
{
ConnectCallback = IPv4ConnectAsync
});
}
static async ValueTask<Stream> IPv4ConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
// By default, we create dual-mode sockets:
// Socket socket = new Socket(SocketType.Stream, ProtocolType.Tcp);
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay = true;
try
{
await socket.ConnectAsync(context.DnsEndPoint, cancellationToken).ConfigureAwait(false);
return new NetworkStream(socket, ownsSocket: true);
}
catch
{
socket.Dispose();
throw;
}
}
```
Best,
Karel Wesseling
How common do we think such environments are?
Should we view that AppService did something exceptional and rather unusual (in which case we should ask them to fix it there), or do we believe there will be more environments where it happens (in which case we should address it in .NET)?
Ugh, that's unfortunate. This is a trivial fix that we should do.
The behavior change you're seeing is because we stopped using the static Socket.ConnectAsync
, and started using the instance variant. The static variant will create a non-dualmode Socket
for each address family, while the instance variant operates just on the your specific Socket
.
The change will be pretty low risk. How common we think it is should dictate if it goes to servicing. I'm assuming Azure App Service users would be thankful, at least :).
I personally would love to see this in servicing release as we have so far identified three external libraries in use that hide the socket behavior and would all require changes in order for them to work in our app service to vNet scenario. We address our internal vNet servers/services via IP without using internal DNS.
@jonsagara @davebronson @kwesseling and others watching this issue: can you try if this workaround helps?
Your workaround did indeed help in my scenario. Code is executing without error in our Azure app service now.
@antonfirsov given that this is an accidental regression in .NET 5 as @scalablecory points out above, can you please put up a fix in master for .NET 6?
We can then discuss if we need to service .NET 5 or not.
@lahma note that hot fix in .NET 5 is not necessarily the only / the fastest option. Key thing is to find out if App Service is the only environment this will ever happen. If they push fix on their side, servicing .NET 5 may not be needed.
@scalablecory @geoffkizer should we revert to 3.1-compatible (non dual-stack) connection logic, which means that we shall start using the static variant all the way down? Or should we still try to attempt dual-stack whenever possible, and use the old logic only as fallback?
Note that with the static variant our current simple DefaultConnectAsync
code will grow complicated again, and will be harder to utilize as a baseline sample code by users who want to override ConnectCallback
:
It's only a small minority of the users who actually need this code.
I would revert to the old logic. Just copy the code from 3.1 -- iirc it has a non-obvious optimization re: CancellationTokenRegistration allocation.
If we need a simple sample, we should define that directly in docs and not rely on the implementation.
should we use specific socket only when we know if it is _only_ IPv4 or V6? It seems like we would do general fix for work-around one particular broken environment.
@wfurt the problem is that we know this only after the host is resolved.
I think we have an API gap in sockets, missing is a Task-based static ConnectAsync
overload:
C#
public class Socket {
public static ValueTask<Socket> ConnectAsync(EndPoint endPoint, SocketType socketType, ProtocolType protocolType, CancellationToken cancellationToken = default);
}
This could provide the required functionality in a compact way. I'm happy to file an API proposal if you guys agree.
I'm happy to file an API proposal if you guys agree.
Sounds good to me. Put the ConnectAlgorithm
there too. (https://github.com/dotnet/runtime/issues/33418)
Possibly related failure in AWS -- https://twitter.com/bhop2112/status/1334943693179117570
@scalablecory I can confirm that the ConnectCallback workaround posted above by @antonfirsov resolved the issue for me. Here are some additional details that may be relevant:
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. (<redacted>: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|283_0(AwaitableSocketAsyncEventArgs saea, ValueTask connectTask, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.DefaultConnectAsync(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, CancellationToken cancellationToken)
--- End of inner exception stack trace ---
at System.Net.Http.ConnectHelper.ConnectAsync(Func`3 callback, DnsEndPoint endPoint, HttpRequestMessage requestMessage, 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.GetHttpConnectionAsync(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken)
at System.Net.Http.HttpConnectionPool.SendWithRetryAsync(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.SendAsyncCore(HttpRequestMessage request, HttpCompletionOption completionOption, Boolean async, Boolean emitTelemetryStartStop, CancellationToken cancellationToken)
@jonsagara I believe you can use your own HttpClient with Elasticsearch NEST, if that helps in the meantime:
https://www.elastic.co/guide/en/elasticsearch/client/net-api/current/modifying-default-connection.html
https://github.com/elastic/elasticsearch-net/tree/master/src/Elastic.Transport/Components/Connection
FWIW, re the service patch for .NET 5, our org would have hit this in the next month or so when we start moving some stuff to .NET 5 as we have pretty much this same configuration as the OP:
I have an ASP.NET Core application running in Azure App Service. It makes calls to Elasticsearch running on an Ubuntu Server 16.04 LTS Azure Virtual Machine, connected to the App Service via an Azure VNet.
I think we have enough for servicing.
@antonfirsov can you make a PR for 5.0 once the 6.0 one is merged, and we can talk to shiproom.
Most helpful comment
Ugh, that's unfortunate. This is a trivial fix that we should do.
The behavior change you're seeing is because we stopped using the static
Socket.ConnectAsync
, and started using the instance variant. The static variant will create a non-dualmodeSocket
for each address family, while the instance variant operates just on the your specificSocket
.https://github.com/dotnet/runtime/blob/f066972c7aa9f9147347623617e789e70aff3d3f/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnectionPool.cs#L1285-L1300
The change will be pretty low risk. How common we think it is should dictate if it goes to servicing. I'm assuming Azure App Service users would be thankful, at least :).