diff --git a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Unix.cs b/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Unix.cs deleted file mode 100644 index 88e5af18ba215c..00000000000000 --- a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Unix.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Net.Sockets; - -namespace System.Net.Internals -{ - internal static partial class SocketExceptionFactory - { - public static SocketException CreateSocketException(SocketError errorCode, int platformError) - { - return new ExtendedSocketException(errorCode, platformError); - } - } -} diff --git a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Windows.cs b/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Windows.cs deleted file mode 100644 index 705157bcdd8b4b..00000000000000 --- a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.Windows.cs +++ /dev/null @@ -1,15 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Net.Sockets; - -namespace System.Net.Internals -{ - internal static partial class SocketExceptionFactory - { - public static SocketException CreateSocketException(SocketError errorCode, int platformError) - { - return new SocketException((int)errorCode); - } - } -} diff --git a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.cs b/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.cs deleted file mode 100644 index b3e160c13d47ad..00000000000000 --- a/src/libraries/Common/src/System/Net/Internals/SocketExceptionFactory.cs +++ /dev/null @@ -1,35 +0,0 @@ -// Licensed to the .NET Foundation under one or more agreements. -// The .NET Foundation licenses this file to you under the MIT license. - -using System.Net.Sockets; - -namespace System.Net.Internals -{ - internal static partial class SocketExceptionFactory - { - private sealed class ExtendedSocketException : SocketException - { - private readonly EndPoint? _endPoint; - - public ExtendedSocketException(int errorCode, EndPoint? endPoint) - : base(errorCode) - { - _endPoint = endPoint; - } - - public ExtendedSocketException(SocketError socketError, int platformError) - : base((int)socketError) - { - HResult = platformError; - } - - public override string Message => - (_endPoint == null) ? base.Message : base.Message + " " + _endPoint.ToString(); - } - - public static SocketException CreateSocketException(int socketError, EndPoint? endPoint) - { - return new ExtendedSocketException(socketError, endPoint); - } - } -} diff --git a/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Unix.cs b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Unix.cs new file mode 100644 index 00000000000000..ae503310856720 --- /dev/null +++ b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Unix.cs @@ -0,0 +1,21 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Sockets +{ + internal static partial class SocketExceptionFactory + { + public static SocketException CreateSocketException(int socketError, EndPoint endPoint) + { + int nativeErr = (int)socketError; + + // If an interop error was not found, then don't invoke Info().RawErrno as that will fail with assert. + if (SocketErrorPal.TryGetNativeErrorForSocketError((SocketError)socketError, out Interop.Error interopErr)) + { + nativeErr = interopErr.Info().RawErrno; + } + + return new SocketException(socketError, CreateMessage(nativeErr, endPoint)); + } + } +} diff --git a/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Windows.cs b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Windows.cs new file mode 100644 index 00000000000000..ceb51ddf46fc34 --- /dev/null +++ b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.Windows.cs @@ -0,0 +1,14 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +namespace System.Net.Sockets +{ + internal static partial class SocketExceptionFactory + { + public static SocketException CreateSocketException(int socketError, EndPoint endPoint) + { + // Windows directly maps socketError to native error code. + return new SocketException(socketError, CreateMessage(socketError, endPoint)); + } + } +} diff --git a/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.cs b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.cs new file mode 100644 index 00000000000000..1c408e8c45b20b --- /dev/null +++ b/src/libraries/Common/src/System/Net/Sockets/SocketExceptionFactory.cs @@ -0,0 +1,15 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Runtime.InteropServices; + +namespace System.Net.Sockets +{ + internal static partial class SocketExceptionFactory + { + private static string CreateMessage(int nativeSocketError, EndPoint endPoint) + { + return Marshal.GetPInvokeErrorMessage(nativeSocketError) + " " + endPoint.ToString(); + } + } +} diff --git a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs index 00250e07d8f328..6f2dca85a1b6d0 100644 --- a/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs +++ b/src/libraries/System.Net.Primitives/ref/System.Net.Primitives.cs @@ -471,6 +471,7 @@ public partial class SocketException : System.ComponentModel.Win32Exception { public SocketException() { } public SocketException(int errorCode) { } + public SocketException(int errorCode, string? message) { } protected SocketException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext) { } public override int ErrorCode { get { throw null; } } public override string Message { get { throw null; } } diff --git a/src/libraries/System.Net.Primitives/src/System/Net/SocketException.cs b/src/libraries/System.Net.Primitives/src/System/Net/SocketException.cs index 3e5540419d03b1..46f4d4305124b3 100644 --- a/src/libraries/System.Net.Primitives/src/System/Net/SocketException.cs +++ b/src/libraries/System.Net.Primitives/src/System/Net/SocketException.cs @@ -30,12 +30,23 @@ public SocketException(int errorCode) : this((SocketError)errorCode) // but that's the least bad option right now. } + /// Initializes a new instance of the class with the specified error code and optional message. + public SocketException(int errorCode, string? message) : this((SocketError)errorCode, message) + { + } + /// Creates a new instance of the class with the specified error code as SocketError. internal SocketException(SocketError socketError) : base(GetNativeErrorForSocketError(socketError)) { _errorCode = socketError; } + /// Initializes a new instance of the class with the specified error code as SocketError and optional message. + internal SocketException(SocketError socketError, string? message) : base(GetNativeErrorForSocketError(socketError), message) + { + _errorCode = socketError; + } + public override string Message => base.Message; public SocketError SocketErrorCode => _errorCode; diff --git a/src/libraries/System.Net.Primitives/tests/FunctionalTests/SocketExceptionTest.cs b/src/libraries/System.Net.Primitives/tests/FunctionalTests/SocketExceptionTest.cs new file mode 100644 index 00000000000000..920df922cf0d65 --- /dev/null +++ b/src/libraries/System.Net.Primitives/tests/FunctionalTests/SocketExceptionTest.cs @@ -0,0 +1,34 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Net.Sockets; +using Xunit; + +namespace System.Net.Primitives.Functional.Tests +{ + public static class SocketExceptionTest + { + [Fact] + public static void Create_AllErrorCodes_Success() + { + foreach (SocketError error in Enum.GetValues(typeof(SocketError))) + { + SocketException e = new SocketException((int)error); + Assert.Equal(error, e.SocketErrorCode); + Assert.Null(e.InnerException); + Assert.NotNull(e.Message); + } + } + + [Fact] + public static void Create_ExceptionWithMessage_Success() + { + const string Message = "Hello World"; + SocketException e = new SocketException((int)SocketError.AccessDenied, Message); + Assert.Equal(SocketError.AccessDenied, e.SocketErrorCode); + Assert.Null(e.InnerException); + Assert.Equal(Message, e.Message); + Assert.Contains(Message, e.ToString()); + } + } +} diff --git a/src/libraries/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj b/src/libraries/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj index 4e2e897b0a6419..d31b916f781f33 100644 --- a/src/libraries/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj +++ b/src/libraries/System.Net.Primitives/tests/FunctionalTests/System.Net.Primitives.Functional.Tests.csproj @@ -25,7 +25,7 @@ - + + \ No newline at end of file diff --git a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj index 7f9c5ae276aa97..4b3af09ab75f95 100644 --- a/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj +++ b/src/libraries/System.Net.Sockets/src/System.Net.Sockets.csproj @@ -78,8 +78,8 @@ Link="Common\System\Net\Internals\IPEndPointExtensions.cs" /> - + + @@ -278,6 +280,8 @@ Link="Common\Interop\Unix\System.Native\Interop.Pipe.cs" /> + diff --git a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs index f479d5d37ddccd..7f9fb007e74e7b 100644 --- a/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs +++ b/src/libraries/System.Net.Sockets/tests/FunctionalTests/Connect.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; +using System.Runtime.InteropServices; using Xunit; using Xunit.Abstractions; using Xunit.Sdk; @@ -218,6 +219,28 @@ public ConnectApm(ITestOutputHelper output) : base(output) {} public sealed class ConnectTask : Connect { public ConnectTask(ITestOutputHelper output) : base(output) {} + + [OuterLoop] + [Fact] + public static void Connect_ThrowSocketException_Success() + { + using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) + { + int anonymousPort = socket.BindToAnonymousPort(IPAddress.Loopback); + IPEndPoint ep = new IPEndPoint(IPAddress.Loopback, anonymousPort); + Assert.ThrowsAsync(() => socket.ConnectAsync(ep)); + try + { + socket.Connect(ep); + Assert.Fail("Socket Connect should throw SocketException in this case."); + } + catch (SocketException ex) + { + Assert.Contains(Marshal.GetPInvokeErrorMessage(ex.NativeErrorCode), ex.Message); + Assert.Contains(ep.ToString(), ex.Message); + } + } + } } public sealed class ConnectEap : Connect