using System.Net.Sockets; using System.Security.Authentication; using MoShiro.Protocol; namespace MoShiro.Agent; /// 控制连接在已建立之后停止应答:对端不认这个会话了,或者线路中断超过了空闲上限。 public sealed class ControlSilentException(string message) : IOException(message); /// 把连接各阶段的异常归类成上报给面板的 :失败发生在哪个阶段、属于哪一类。 public static class LinkFaults { private const int MaxMessage = 200; public static LinkFault From(Exception ex, string phase) { Exception inner = ex is AggregateException { InnerExceptions.Count: 1 } aggregate ? aggregate.InnerExceptions[0] : ex; string kind = inner switch { ControlSilentException => "silent", OperationCanceledException or TimeoutException => "timeout", AuthenticationException => "tls", SocketException socket => Kind(socket.SocketErrorCode), IOException { InnerException: SocketException socket } => Kind(socket.SocketErrorCode), InvalidOperationException invalid when invalid.Message.Contains("authentication", StringComparison.OrdinalIgnoreCase) || invalid.Message.Contains("challenge", StringComparison.OrdinalIgnoreCase) => "auth", IOException => "closed", _ => "other", }; string message = kind == "timeout" && inner is OperationCanceledException ? $"{phase} did not finish in time" : inner.Message; return new LinkFault { Phase = phase, Kind = kind, Message = message.Length > MaxMessage ? message[..MaxMessage] : message }; } public static LinkFault Closed(string phase, string message) => new() { Phase = phase, Kind = "closed", Message = message }; private static string Kind(SocketError error) => error switch { SocketError.ConnectionRefused => "refused", SocketError.ConnectionReset or SocketError.ConnectionAborted => "reset", SocketError.HostNotFound or SocketError.NoData or SocketError.TryAgain => "dns", SocketError.TimedOut => "timeout", _ => "other", }; }