diff --git a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs index ed70a3797a..7faf486d1f 100644 --- a/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs +++ b/src/Kestrel.Core/Internal/Infrastructure/KestrelEventSource.cs @@ -32,8 +32,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure { ConnectionStart( connection.ConnectionId, - connection.LocalEndPoint.ToString(), - connection.RemoteEndPoint.ToString()); + connection.LocalEndPoint?.ToString(), + connection.RemoteEndPoint?.ToString()); } } diff --git a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs index 0c85a2a928..b550c852c7 100644 --- a/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs +++ b/test/Kestrel.FunctionalTests/AddressRegistrationTests.cs @@ -40,7 +40,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [ConditionalFact] - [NetworkIsReachable] + [HostNameIsReachable] public async Task RegisterAddresses_HostName_Success() { var hostName = Dns.GetHostName(); @@ -308,7 +308,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests } [ConditionalFact] - [NetworkIsReachable] + [HostNameIsReachable] public async Task ListenAnyIP_HostName_Success() { var hostName = Dns.GetHostName(); diff --git a/test/Kestrel.FunctionalTests/TestHelpers/HostNameIsReachableAttribute.cs b/test/Kestrel.FunctionalTests/TestHelpers/HostNameIsReachableAttribute.cs new file mode 100644 index 0000000000..bd120b6356 --- /dev/null +++ b/test/Kestrel.FunctionalTests/TestHelpers/HostNameIsReachableAttribute.cs @@ -0,0 +1,88 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Net; +using System.Net.Sockets; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Testing.xunit; + +namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests +{ + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class HostNameIsReachableAttribute : Attribute, ITestCondition + { + private string _hostname; + private string _error; + private bool? _isMet; + + public bool IsMet + { + get + { + return _isMet ?? (_isMet = HostNameIsReachable().GetAwaiter().GetResult()).Value; + } + } + + public string SkipReason => _hostname != null + ? $"Test cannot run when network is unreachable. Socket exception: '{_error}'" + : "Could not determine hostname for current test machine"; + + private async Task HostNameIsReachable() + { + try + { + _hostname = Dns.GetHostName(); + + // if the network is unreachable on macOS, throws with SocketError.NetworkUnreachable + // if the network device is not configured, throws with SocketError.HostNotFound + // if the network is reachable, throws with SocketError.ConnectionRefused or succeeds + var timeoutTask = Task.Delay(1000); + if (await Task.WhenAny(ConnectToHost(_hostname, 80), timeoutTask) == timeoutTask) + { + _error = "Attempt to establish a connection took over a second without success or failure."; + return false; + } + } + catch (SocketException ex) when ( + ex.SocketErrorCode == SocketError.NetworkUnreachable + || ex.SocketErrorCode == SocketError.HostNotFound) + { + _error = ex.Message; + return false; + } + catch + { + // Swallow other errors. Allows the test to throw the failures instead + } + + return true; + } + + public static async Task ConnectToHost(string hostName, int port) + { + var tcs = new TaskCompletionSource(); + + var socketArgs = new SocketAsyncEventArgs(); + socketArgs.RemoteEndPoint = new DnsEndPoint(hostName, port); + socketArgs.Completed += (s, e) => tcs.TrySetResult(e.ConnectSocket); + + // Must use static ConnectAsync(), since instance Connect() does not support DNS names on OSX/Linux. + if (Socket.ConnectAsync(SocketType.Stream, ProtocolType.Tcp, socketArgs)) + { + await tcs.Task.ConfigureAwait(false); + } + + var socket = socketArgs.ConnectSocket; + + if (socket == null) + { + throw new SocketException((int)socketArgs.SocketError); + } + else + { + return socket; + } + } + } +} diff --git a/test/Kestrel.FunctionalTests/TestHelpers/NetworkIsReachableAttribute.cs b/test/Kestrel.FunctionalTests/TestHelpers/NetworkIsReachableAttribute.cs deleted file mode 100644 index 9b62c0689b..0000000000 --- a/test/Kestrel.FunctionalTests/TestHelpers/NetworkIsReachableAttribute.cs +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Net; -using System.Net.Sockets; -using Microsoft.AspNetCore.Testing; -using Microsoft.AspNetCore.Testing.xunit; - -namespace Microsoft.AspNetCore.Server.Kestrel.FunctionalTests -{ - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class NetworkIsReachableAttribute : Attribute, ITestCondition - { - private string _hostname; - private string _error; - - public bool IsMet - { - get - { - try - { - _hostname = Dns.GetHostName(); - - // if the network is unreachable on macOS, throws with SocketError.NetworkUnreachable - // if the network device is not configured, throws with SocketError.HostNotFound - // if the network is reachable, throws with SocketError.ConnectionRefused or succeeds - HttpClientSlim.GetStringAsync($"http://{_hostname}").GetAwaiter().GetResult(); - } - catch (SocketException ex) when ( - ex.SocketErrorCode == SocketError.NetworkUnreachable - || ex.SocketErrorCode == SocketError.HostNotFound) - { - _error = ex.Message; - return false; - } - catch - { - // Swallow other errors. Allows the test to throw the failures instead - } - - return true; - } - } - - public string SkipReason => _hostname != null - ? $"Test cannot run when network is unreachable. Socket exception: '{_error}'" - : "Could not determine hostname for current test machine"; - } -}