| | | 1 | | // Copyright (c) ZeroC, Inc. |
| | | 2 | | |
| | | 3 | | using System.Net.Sockets; |
| | | 4 | | |
| | | 5 | | namespace IceRpc.Transports; |
| | | 6 | | |
| | | 7 | | /// <summary>Provides an extension method for <see cref="SocketException"/> to convert it into an <see |
| | | 8 | | /// cref="IceRpcException"/>.</summary> |
| | | 9 | | public static class SocketExceptionExtensions |
| | | 10 | | { |
| | | 11 | | /// <summary>Converts a <see cref="SocketException"/> into an <see cref="IceRpcException" />.</summary> |
| | | 12 | | /// <param name="exception">The exception to convert.</param> |
| | | 13 | | /// <param name="innerException">The inner exception for the <see cref="IceRpcException"/>, when |
| | | 14 | | /// <see langword="null"/> <paramref name="exception"/> is used as the inner exception.</param> |
| | | 15 | | /// <returns>The <see cref="IceRpcException"/> created from the <see cref="SocketException"/>.</returns> |
| | | 16 | | public static IceRpcException ToIceRpcException(this SocketException exception, Exception? innerException = null) |
| | 41 | 17 | | { |
| | 41 | 18 | | innerException ??= exception; |
| | 41 | 19 | | IceRpcError errorCode = exception.SocketErrorCode switch |
| | 41 | 20 | | { |
| | 41 | 21 | | // Address is already in use when attempting to bind a listening socket. |
| | 3 | 22 | | SocketError.AddressAlreadyInUse => IceRpcError.AddressInUse, |
| | 41 | 23 | | |
| | 41 | 24 | | // ConnectionAborted is reported by the OS and is often triggered by the peer or by a network failure. |
| | 0 | 25 | | SocketError.ConnectionAborted => IceRpcError.ConnectionAborted, |
| | 41 | 26 | | |
| | 41 | 27 | | // Shutdown matches EPIPE and ConnectionReset matches ECONNRESET. Both are the result of the peer |
| | 41 | 28 | | // closing the connection non-gracefully. |
| | 41 | 29 | | // |
| | 41 | 30 | | // EPIPE is returned when writing to a socket that has been closed and the send buffer is empty. |
| | 41 | 31 | | // ECONNRESET is returned when the connection is reset while data is still pending in the send buffer. |
| | 41 | 32 | | // |
| | 41 | 33 | | // In both cases the connection can no longer be used. |
| | 33 | 34 | | SocketError.ConnectionReset => IceRpcError.ConnectionAborted, |
| | 2 | 35 | | SocketError.Shutdown => IceRpcError.ConnectionAborted, |
| | 41 | 36 | | |
| | 41 | 37 | | // NetworkReset indicates that an established connection became invalid due to a network event |
| | 41 | 38 | | // (for example interface reset, Wi-Fi reconnect, VPN reconnect). |
| | 0 | 39 | | SocketError.NetworkReset => IceRpcError.ConnectionAborted, |
| | 41 | 40 | | |
| | 41 | 41 | | // The server is reachable but no process is listening on the target port. |
| | 2 | 42 | | SocketError.ConnectionRefused => IceRpcError.ConnectionRefused, |
| | 41 | 43 | | |
| | 41 | 44 | | // These errors indicate the remote server cannot be reached. This includes routing failures, |
| | 41 | 45 | | // local network failures, OS-level TCP timeouts, and host reachability failures. |
| | 41 | 46 | | // |
| | 41 | 47 | | // The TimedOut error refers to the OS TCP stack exhausting SYN retransmissions when |
| | 41 | 48 | | // attempting to connect — not to IceRPC-level timeouts, which result in |
| | 41 | 49 | | // OperationCanceledException via CancellationToken. |
| | 41 | 50 | | // |
| | 41 | 51 | | // With the async socket API used by IceRPC, these errors occur during connection |
| | 41 | 52 | | // establishment. On an established TCP connection, the kernel absorbs ICMP errors |
| | 41 | 53 | | // and retransmits internally; the application sees ConnectionReset (mapped to |
| | 41 | 54 | | // ConnectionAborted above) rather than these reachability errors. |
| | 41 | 55 | | // |
| | 41 | 56 | | // They are grouped as ServerUnreachable because from the RPC perspective the connection |
| | 41 | 57 | | // cannot be established. |
| | 1 | 58 | | SocketError.HostUnreachable => IceRpcError.ServerUnreachable, |
| | 0 | 59 | | SocketError.NetworkUnreachable => IceRpcError.ServerUnreachable, |
| | 0 | 60 | | SocketError.NetworkDown => IceRpcError.ServerUnreachable, |
| | 0 | 61 | | SocketError.HostDown => IceRpcError.ServerUnreachable, |
| | 0 | 62 | | SocketError.TimedOut => IceRpcError.ServerUnreachable, |
| | 41 | 63 | | |
| | 41 | 64 | | // Name resolution failures are also mapped to ServerUnreachable so that the connection cache |
| | 41 | 65 | | // can treat DNS failures and transport reachability failures uniformly and try alternate |
| | 41 | 66 | | // server addresses. |
| | 41 | 67 | | // |
| | 41 | 68 | | // These errors always occur before connection establishment (during name resolution). |
| | 41 | 69 | | // |
| | 41 | 70 | | // Some of these errors (for example HostNotFound) are often permanent configuration issues, |
| | 41 | 71 | | // but IceRPC keeps the public error model small and leaves retry decisions to higher layers. |
| | 0 | 72 | | SocketError.HostNotFound => IceRpcError.ServerUnreachable, |
| | 0 | 73 | | SocketError.TryAgain => IceRpcError.ServerUnreachable, |
| | 0 | 74 | | SocketError.NoRecovery => IceRpcError.ServerUnreachable, |
| | 0 | 75 | | SocketError.NoData => IceRpcError.ServerUnreachable, |
| | 41 | 76 | | |
| | 41 | 77 | | // Operation was aborted locally, typically due to cancellation or socket disposal. |
| | 0 | 78 | | SocketError.OperationAborted => IceRpcError.OperationAborted, |
| | 41 | 79 | | |
| | 0 | 80 | | _ => IceRpcError.IceRpcError |
| | 41 | 81 | | }; |
| | | 82 | | |
| | 41 | 83 | | return new IceRpcException(errorCode, innerException); |
| | 41 | 84 | | } |
| | | 85 | | } |