diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..afdb1f3b --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# CA1416: 验证平台兼容性 +dotnet_diagnostic.CA1416.severity = none diff --git a/cmonitor.libs/cmonitor.libs.csproj b/cmonitor.libs/cmonitor.libs.csproj index 1dce156b..94859a76 100644 --- a/cmonitor.libs/cmonitor.libs.csproj +++ b/cmonitor.libs/cmonitor.libs.csproj @@ -2,10 +2,11 @@ Library - net7;net8 + net8 false true Debug;Release;ReleaseNetwork;ReleaseMonitor + true DEBUG;TRACE @@ -27,15 +28,6 @@ none false - - embedded - - - embedded - - - embedded - embedded @@ -45,9 +37,7 @@ embedded - - embedded - + embedded diff --git a/cmonitor.llock.win/cmonitor.llock.win.csproj b/cmonitor.llock.win/cmonitor.llock.win.csproj index f03fe184..46eb340b 100644 --- a/cmonitor.llock.win/cmonitor.llock.win.csproj +++ b/cmonitor.llock.win/cmonitor.llock.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows disable true enable diff --git a/cmonitor.message.win/cmonitor.message.win.csproj b/cmonitor.message.win/cmonitor.message.win.csproj index cd0fd566..ad7c00fd 100644 --- a/cmonitor.message.win/cmonitor.message.win.csproj +++ b/cmonitor.message.win/cmonitor.message.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows disable true enable @@ -11,26 +11,10 @@ Debug;Release;ReleaseNetwork;ReleaseMonitor - - embedded - - embedded - - embedded - - - - embedded - - - - embedded - - embedded diff --git a/cmonitor.network.service/cmonitor.network.service.csproj b/cmonitor.network.service/cmonitor.network.service.csproj index e3392cf7..4726f094 100644 --- a/cmonitor.network.service/cmonitor.network.service.csproj +++ b/cmonitor.network.service/cmonitor.network.service.csproj @@ -2,7 +2,7 @@ Exe - net7.0;net8.0 + net8.0 enable disable true diff --git a/cmonitor.notify.win/cmonitor.notify.win.csproj b/cmonitor.notify.win/cmonitor.notify.win.csproj index d16d2372..cc7ef8ac 100644 --- a/cmonitor.notify.win/cmonitor.notify.win.csproj +++ b/cmonitor.notify.win/cmonitor.notify.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows disable true enable @@ -11,26 +11,10 @@ Debug;Release;ReleaseNetwork;ReleaseMonitor - - embedded - - embedded - - embedded - - - - embedded - - - - embedded - - embedded diff --git a/cmonitor.sas.service/cmonitor.sas.service.csproj b/cmonitor.sas.service/cmonitor.sas.service.csproj index c9a82788..6381eb5f 100644 --- a/cmonitor.sas.service/cmonitor.sas.service.csproj +++ b/cmonitor.sas.service/cmonitor.sas.service.csproj @@ -2,7 +2,7 @@ Exe - net7.0;net8.0 + net8.0 enable disable true diff --git a/cmonitor.sln b/cmonitor.sln index 85774f35..2e92fb75 100644 --- a/cmonitor.sln +++ b/cmonitor.sln @@ -29,6 +29,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmonitor.tests", "cmonitor. EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "cmonitor.network.service", "cmonitor.network.service\cmonitor.network.service.csproj", "{E8AB5039-3A42-424F-AAEC-A102C8CAA305}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{D2831E12-7497-4B7B-ADFD-7C327C5622D3}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/cmonitor.snatch.win/cmonitor.snatch.win.csproj b/cmonitor.snatch.win/cmonitor.snatch.win.csproj index e885d0a6..35713a8a 100644 --- a/cmonitor.snatch.win/cmonitor.snatch.win.csproj +++ b/cmonitor.snatch.win/cmonitor.snatch.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows disable true enable @@ -11,25 +11,10 @@ app.manifest - - embedded - - embedded - - embedded - - - - embedded - - - - embedded - embedded diff --git a/cmonitor.viewer.server.win/cmonitor.viewer.server.win.csproj b/cmonitor.viewer.server.win/cmonitor.viewer.server.win.csproj index 141f579f..1d7129fe 100644 --- a/cmonitor.viewer.server.win/cmonitor.viewer.server.win.csproj +++ b/cmonitor.viewer.server.win/cmonitor.viewer.server.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows win-x64;win-arm64 disable true @@ -12,25 +12,10 @@ Debug;Release;ReleaseNetwork;ReleaseMonitor - - embedded - - embedded - - embedded - - - - embedded - - - - embedded - embedded diff --git a/cmonitor.wallpaper.win/cmonitor.wallpaper.win.csproj b/cmonitor.wallpaper.win/cmonitor.wallpaper.win.csproj index d086e722..48542b5d 100644 --- a/cmonitor.wallpaper.win/cmonitor.wallpaper.win.csproj +++ b/cmonitor.wallpaper.win/cmonitor.wallpaper.win.csproj @@ -2,7 +2,7 @@ WinExe - net7.0-windows;net8.0-windows + net8.0-windows disable true enable @@ -11,25 +11,10 @@ Debug;Release;ReleaseNetwork;ReleaseMonitor - - embedded - - embedded - - embedded - - - - embedded - - - - embedded - embedded diff --git a/cmonitor/client/tunnel/ITunnelConnection.cs b/cmonitor/client/tunnel/ITunnelConnection.cs index b0265145..7db02976 100644 --- a/cmonitor/client/tunnel/ITunnelConnection.cs +++ b/cmonitor/client/tunnel/ITunnelConnection.cs @@ -3,7 +3,9 @@ using common.libs.extends; using System.Buffers; using System.IO.Pipelines; using System.Net; +using System.Net.Quic; using System.Net.Security; +using System.Net.Sockets; using System.Text.Json.Serialization; namespace cmonitor.client.tunnel @@ -270,4 +272,217 @@ namespace cmonitor.client.tunnel } } + + public sealed class TunnelConnectionQuic : ITunnelConnection + { + public TunnelConnectionQuic() + { + } + + public string RemoteMachineName { get; init; } + public string TransactionId { get; init; } + public string TransportName { get; init; } + public string Label { get; init; } + public TunnelMode Mode { get; init; } + public TunnelProtocolType ProtocolType { get; init; } + public TunnelType Type { get; init; } + public TunnelDirection Direction { get; init; } + public IPEndPoint IPEndPoint { get; init; } + + public bool Connected => Stream != null && Stream.CanWrite; + + [JsonIgnore] + public QuicStream Stream { get; init; } + + + private ITunnelConnectionReceiveCallback callback; + private CancellationTokenSource cancellationTokenSource; + private object userToken; + private bool framing; + private Pipe pipe; + private ReceiveDataBuffer bufferCache = new ReceiveDataBuffer(); + + /// + /// 开始接收数据 + /// + /// 数据回调 + /// 自定义数据 + /// 是否处理粘包,true时,请在首部4字节标注数据长度 + public void BeginReceive(ITunnelConnectionReceiveCallback callback, object userToken, bool framing = true) + { + if (this.callback != null) return; + + this.callback = callback; + this.userToken = userToken; + this.framing = framing; + + cancellationTokenSource = new CancellationTokenSource(); + pipe = new Pipe(new PipeOptions(pauseWriterThreshold: 1 * 1024 * 1024, resumeWriterThreshold: 128 * 1024)); + _ = ProcessWrite(); + _ = ProcessReader(); + + } + private async Task ProcessWrite() + { + PipeWriter writer = pipe.Writer; + try + { + while (cancellationTokenSource.IsCancellationRequested == false) + { + Memory buffer = writer.GetMemory(8 * 1024); + int length = await Stream.ReadAsync(buffer, cancellationTokenSource.Token); + if (length == 0) + { + break; + } + writer.Advance(length); + FlushResult result = await writer.FlushAsync(); + if (result.IsCanceled || result.IsCompleted) + { + break; + } + } + } + catch (Exception ex) + { + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Error(ex); + } + } + finally + { + await writer.CompleteAsync(); + Close(); + } + } + private async Task ProcessReader() + { + PipeReader reader = pipe.Reader; + try + { + while (cancellationTokenSource.IsCancellationRequested == false) + { + ReadResult readResult = await reader.ReadAsync(); + ReadOnlySequence buffer = readResult.Buffer; + if (buffer.IsEmpty && readResult.IsCompleted) + { + break; + } + if (buffer.Length > 0) + { + SequencePosition end = await ReadPacket(buffer).ConfigureAwait(false); + reader.AdvanceTo(end); + } + } + } + catch (Exception ex) + { + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Error(ex); + } + } + finally + { + await reader.CompleteAsync(); + Close(); + } + } + private unsafe int ReaderHead(ReadOnlySequence buffer) + { + Span span = stackalloc byte[4]; + buffer.Slice(0, 4).CopyTo(span); + return span.ToInt32(); + } + private async Task ReadPacket(ReadOnlySequence buffer) + { + //不分包 + if (framing == false) + { + foreach (var memory in buffer) + { + try + { + await callback.Receive(this, memory, this.userToken).ConfigureAwait(false); + } + catch (Exception) + { + } + } + return buffer.End; + } + + //分包 + while (buffer.Length > 4) + { + //读取头 + int length = ReaderHead(buffer); + if (buffer.Length < length + 4) + { + break; + } + + //拼接数据 + ReadOnlySequence cache = buffer.Slice(4, length); + foreach (var memory in cache) + { + bufferCache.AddRange(memory); + } + + try + { + await callback.Receive(this, bufferCache.Data.Slice(0, bufferCache.Size), this.userToken).ConfigureAwait(false); + } + catch (Exception) + { + } + bufferCache.Clear(); + + //分割去掉已使用的数据 + buffer = buffer.Slice(4 + length); + } + return buffer.Start; + } + + + private SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1); + public async Task SendAsync(ReadOnlyMemory data) + { + await semaphoreSlim.WaitAsync(); + try + { + await Stream.WriteAsync(data, cancellationTokenSource.Token); + await Stream.FlushAsync(); + } + catch (Exception ex) + { + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Error(ex); + } + } + finally + { + semaphoreSlim.Release(); + } + } + + public void Close() + { + callback = null; + userToken = null; + cancellationTokenSource?.Cancel(); + pipe = null; + bufferCache.Clear(true); + + Stream?.Close(); + Stream?.Dispose(); + } + + public override string ToString() + { + return $"TransactionId:{TransactionId},TransportName:{TransportName},ProtocolType:{ProtocolType},Type:{Type},Direction:{Direction},IPEndPoint:{IPEndPoint},RemoteMachineName:{RemoteMachineName}"; + } + } } diff --git a/cmonitor/cmonitor.csproj b/cmonitor/cmonitor.csproj index 4be9f920..47e93afb 100644 --- a/cmonitor/cmonitor.csproj +++ b/cmonitor/cmonitor.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net7.0 + net8.0 enable disable true @@ -15,6 +15,7 @@ true true cmonitor.snk + true @@ -107,6 +108,9 @@ Always + + + @@ -121,7 +125,7 @@ - + diff --git a/cmonitor/plugins/tunnel/server/TunnelBindServer.cs b/cmonitor/plugins/tunnel/server/TunnelBindServer.cs index de8d56fb..a64acb1a 100644 --- a/cmonitor/plugins/tunnel/server/TunnelBindServer.cs +++ b/cmonitor/plugins/tunnel/server/TunnelBindServer.cs @@ -3,13 +3,14 @@ using common.libs.extends; using System.Collections.Concurrent; using System.Net; using System.Net.Sockets; +using System.Text; namespace cmonitor.plugins.tunnel.server { public sealed class TunnelBindServer { - public Func OnTcpConnected { get; set; } = async (state, socket) => { await Task.CompletedTask; }; - public Func OnUdpConnected { get; set; } = async (state, udpClient) => { await Task.CompletedTask; }; + public Func OnTcpConnected { get; set; } = async (state, socket) => { await Task.CompletedTask; }; + public Func OnUdpConnected { get; set; } = async (state, udpClient) => { await Task.CompletedTask; }; private ConcurrentDictionary acceptBinds = new ConcurrentDictionary(); @@ -107,9 +108,21 @@ namespace cmonitor.plugins.tunnel.server try { IPEndPoint ep = new IPEndPoint(IPAddress.Any, IPEndPoint.MinPort); - byte[] _ = token.UdpClient.EndReceive(result, ref ep); + byte[] bytes = token.UdpClient.EndReceive(result, ref ep); + string command = Encoding.UTF8.GetString(bytes); - OnUdpConnected(token.State, token.UdpClient); + if (command == "snltty.end") + { + OnUdpConnected(token.State, token.UdpClient); + return; + } + else if (command == "snltty.test") + { + token.UdpClient.Send(bytes); + } + + + result = token.UdpClient.BeginReceive(ReceiveCallbackUdp, token); } catch (Exception) { diff --git a/cmonitor/plugins/tunnel/transport/TransportTcpNutssb.cs b/cmonitor/plugins/tunnel/transport/TransportTcpNutssb.cs index c6ce2df6..680769cc 100644 --- a/cmonitor/plugins/tunnel/transport/TransportTcpNutssb.cs +++ b/cmonitor/plugins/tunnel/transport/TransportTcpNutssb.cs @@ -68,6 +68,7 @@ namespace cmonitor.plugins.tunnel.transport BindAndTTL(tunnelTransportInfo1); if (await OnSendConnectBegin(tunnelTransportInfo1) == false) { + tunnelBindServer.RemoveBind(tunnelTransportInfo1.Local.Local.Port, true); return null; } ITunnelConnection connection = await WaitReverse(tunnelTransportInfo1); @@ -76,8 +77,10 @@ namespace cmonitor.plugins.tunnel.transport await OnSendConnectSuccess(tunnelTransportInfo); return connection; } + tunnelBindServer.RemoveBind(tunnelTransportInfo1.Local.Local.Port, true); } + await OnSendConnectFail(tunnelTransportInfo); return null; } @@ -159,35 +162,18 @@ namespace cmonitor.plugins.tunnel.transport foreach (IPEndPoint ep in eps.Where(c => NotIPv6Support(c.Address) == false)) { Socket targetSocket = new(ep.AddressFamily, SocketType.Stream, System.Net.Sockets.ProtocolType.Tcp); - targetSocket.IPv6Only(ep.Address.AddressFamily, false); - targetSocket.KeepAlive(); - targetSocket.ReuseBind(new IPEndPoint(ep.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, tunnelTransportInfo.Local.Local.Port)); - IAsyncResult result = targetSocket.BeginConnect(ep, null, null); - - if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) - { - Logger.Instance.Warning($"{Name} connect to {tunnelTransportInfo.Remote.MachineName} {ep}"); - } - - int times = ep.Address.Equals(tunnelTransportInfo.Remote.Remote.Address) ? 10 : 5; - for (int i = 0; i < times; i++) - { - if (result.IsCompleted) - { - break; - } - await Task.Delay(20); - } - try { - if (result.IsCompleted == false) - { - targetSocket.SafeClose(); - continue; - } - targetSocket.EndConnect(result); + targetSocket.IPv6Only(ep.Address.AddressFamily, false); + targetSocket.KeepAlive(); + targetSocket.ReuseBind(new IPEndPoint(ep.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, tunnelTransportInfo.Local.Local.Port)); + + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Warning($"{Name} connect to {tunnelTransportInfo.Remote.MachineName} {ep}"); + } + await targetSocket.ConnectAsync(ep).WaitAsync(TimeSpan.FromMilliseconds(ep.Address.Equals(tunnelTransportInfo.Remote.Remote.Address) ? 500 : 100)); SslStream sslStream = new SslStream(new NetworkStream(targetSocket), true, new RemoteCertificateValidationCallback(ValidateServerCertificate), null); await sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions { EnabledSslProtocols = SslProtocols.Tls | SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13 }); @@ -309,7 +295,7 @@ namespace cmonitor.plugins.tunnel.transport } catch (Exception ex) { - if(Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) { Logger.Instance.Error(ex); } diff --git a/cmonitor/plugins/tunnel/transport/TransportUdpQuic.cs b/cmonitor/plugins/tunnel/transport/TransportUdpQuic.cs new file mode 100644 index 00000000..a8b39996 --- /dev/null +++ b/cmonitor/plugins/tunnel/transport/TransportUdpQuic.cs @@ -0,0 +1,502 @@ +using cmonitor.client.tunnel; +using cmonitor.config; +using common.libs; +using common.libs.extends; +using System.Collections.Concurrent; +using System.Net; +using System.Net.Quic; +using System.Net.Security; +using System.Net.Sockets; +using System.Security.Authentication; +using System.Security.Cryptography.X509Certificates; +using System.Text; + +namespace cmonitor.plugins.tunnel.transport +{ + public sealed class TransportUdpQuic : ITunnelTransport + { + public string Name => "quic"; + + public string Label => "基于UDP的Quic"; + + public TunnelProtocolType ProtocolType => TunnelProtocolType.Quic; + + public Func> OnSendConnectBegin { get; set; } = async (info) => { return await Task.FromResult(false); }; + public Func OnSendConnectFail { get; set; } = async (info) => { await Task.CompletedTask; }; + public Func OnSendConnectSuccess { get; set; } = async (info) => { await Task.CompletedTask; }; + public Action OnConnected { get; set; } = (state) => { }; + + private byte[] UdpTtlBytes = Encoding.UTF8.GetBytes("snltty.ttl"); + private byte[] UdpEndBytes = Encoding.UTF8.GetBytes("snltty.end"); + + + private X509Certificate serverCertificate; + private IPEndPoint quicEP = new IPEndPoint(IPAddress.Any, 0); + private ConcurrentDictionary udpListeners = new ConcurrentDictionary(); + + private readonly Config config; + public TransportUdpQuic(Config config) + { + this.config = config; + _ = QuicStart(); + } + + private async Task QuicStart() + { + if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + string path = Path.GetFullPath(config.Data.Client.Tunnel.Certificate); + if (File.Exists(path)) + { + serverCertificate = new X509Certificate(path, config.Data.Client.Tunnel.Password); + } + else + { + Logger.Instance.Error($"file {path} not found"); + Environment.Exit(0); + } + + QuicListener listener = await QuicListener.ListenAsync(new QuicListenerOptions + { + ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + ListenBacklog = int.MaxValue, + ListenEndPoint = new IPEndPoint(IPAddress.Any, 0), + ConnectionOptionsCallback = (connection, hello, token) => + { + return ValueTask.FromResult(new QuicServerConnectionOptions + { + MaxInboundBidirectionalStreams = 65535, + MaxInboundUnidirectionalStreams = 65535, + DefaultCloseErrorCode = 0x0a, + DefaultStreamErrorCode = 0x0b, + ServerAuthenticationOptions = new SslServerAuthenticationOptions + { + ServerCertificate = serverCertificate, + EnabledSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13, + ApplicationProtocols = new List { SslApplicationProtocol.Http3 } + } + }); + } + }); + quicEP = new IPEndPoint(IPAddress.Loopback, listener.LocalEndPoint.Port); + + while (true) + { + QuicConnection quicConnection = await listener.AcceptConnectionAsync(); + QuicStream quicStream = await quicConnection.AcceptInboundStreamAsync(); + if (udpListeners.TryGetValue(quicConnection.RemoteEndPoint.Port, out AsyncUserToken token)) + { + await OnUdpConnected(token.State, quicStream, token.TargetEP); + } + } + } + } + + public async Task ConnectAsync(TunnelTransportInfo tunnelTransportInfo) + { + if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsMacOS()) + { + if (QuicListener.IsSupported == false) + { + await OnSendConnectFail(tunnelTransportInfo); + return null; + } + } + + if (tunnelTransportInfo.Direction == TunnelDirection.Forward) + { + //正向连接 + if (await OnSendConnectBegin(tunnelTransportInfo) == false) + { + return null; + } + await Task.Delay(500); + ITunnelConnection connection = await ConnectForward(tunnelTransportInfo); + if (connection != null) + { + await OnSendConnectSuccess(tunnelTransportInfo); + return connection; + } + } + else if (tunnelTransportInfo.Direction == TunnelDirection.Reverse) + { + //反向连接 + TunnelTransportInfo tunnelTransportInfo1 = tunnelTransportInfo.ToJsonFormat().DeJson(); + BindListen(tunnelTransportInfo1.Local.Local, tunnelTransportInfo1, quicEP); + BindAndTTL(tunnelTransportInfo1); + if (await OnSendConnectBegin(tunnelTransportInfo1) == false) + { + RemoveBind(tunnelTransportInfo1.Local.Local.Port, true); + return null; + } + ITunnelConnection connection = await WaitReverse(tunnelTransportInfo1); + if (connection != null) + { + await OnSendConnectSuccess(tunnelTransportInfo); + return connection; + } + RemoveBind(tunnelTransportInfo1.Local.Local.Port, true); + } + + await OnSendConnectFail(tunnelTransportInfo); + return null; + } + public void OnBegin(TunnelTransportInfo tunnelTransportInfo) + { + if (tunnelTransportInfo.Direction == TunnelDirection.Forward) + { + BindListen(tunnelTransportInfo.Local.Local, tunnelTransportInfo, quicEP); + } + Task.Run(async () => + { + if (tunnelTransportInfo.Direction == TunnelDirection.Forward) + { + BindAndTTL(tunnelTransportInfo); + } + else + { + ITunnelConnection connection = await ConnectForward(tunnelTransportInfo); + if (connection != null) + { + OnConnected(connection); + await OnSendConnectSuccess(tunnelTransportInfo); + } + else + { + await OnSendConnectFail(tunnelTransportInfo); + } + } + }); + } + + + private async Task ConnectForward(TunnelTransportInfo tunnelTransportInfo) + { + //要连接哪些IP + IPAddress[] localIps = tunnelTransportInfo.Remote.LocalIps.Where(c => c.Equals(tunnelTransportInfo.Remote.Local.Address) == false).ToArray(); + List eps = new List(); + //先尝试内网ipv4 + foreach (IPAddress item in localIps.Where(c => c.AddressFamily == AddressFamily.InterNetwork)) + { + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Local.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port + 1)); + } + //在尝试外网 + eps.AddRange(new List{ + new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port), + new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port+1), + }); + //再尝试IPV6 + foreach (IPAddress item in localIps.Where(c => c.AddressFamily == AddressFamily.InterNetworkV6)) + { + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Local.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port + 1)); + } + + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Warning($"{Name} connect to {tunnelTransportInfo.Remote.MachineName} {string.Join("\r\n", eps.Select(c => c.ToString()))}"); + } + + + + foreach (IPEndPoint ep in eps.Where(c => NotIPv6Support(c.Address) == false)) + { + + IPEndPoint local = new IPEndPoint(ep.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, tunnelTransportInfo.Local.Local.Port); + UdpClient udpClient = new UdpClient(local.AddressFamily); + udpClient.Client.ReuseBind(local); + udpClient.Client.WindowsUdpBug(); + try + { + udpClient.Send(UdpTtlBytes, ep); + UdpReceiveResult udpReceiveResult = await udpClient.ReceiveAsync().WaitAsync(TimeSpan.FromMilliseconds(ep.Address.Equals(tunnelTransportInfo.Remote.Remote.Address) ? 500 : 100)); + udpClient.Send(UdpEndBytes, ep); + + int port = BindForward(udpClient, ep); + + QuicConnection connection = await QuicConnection.ConnectAsync(new QuicClientConnectionOptions + { + RemoteEndPoint = new IPEndPoint(IPAddress.Loopback, port), + LocalEndPoint = new IPEndPoint(IPAddress.Any, 0), + DefaultCloseErrorCode = 0x0a, + DefaultStreamErrorCode = 0x0b, + ClientAuthenticationOptions = new SslClientAuthenticationOptions + { + ApplicationProtocols = new List { SslApplicationProtocol.Http3 }, + EnabledSslProtocols = SslProtocols.Tls11 | SslProtocols.Tls12 | SslProtocols.Tls13, + RemoteCertificateValidationCallback = (sender, certificate, chain, errors) => + { + return true; + } + } + }).AsTask().WaitAsync(TimeSpan.FromMilliseconds(ep.Address.Equals(tunnelTransportInfo.Remote.Remote.Address) ? 500 : 100)); + + QuicStream quicStream = await connection.OpenOutboundStreamAsync(QuicStreamType.Bidirectional); + + return new TunnelConnectionQuic + { + Stream = quicStream, + IPEndPoint = ep, + TransactionId = tunnelTransportInfo.TransactionId, + RemoteMachineName = tunnelTransportInfo.Remote.MachineName, + TransportName = Name, + Direction = tunnelTransportInfo.Direction, + ProtocolType = ProtocolType, + Type = TunnelType.P2P, + Mode = TunnelMode.Client, + Label = string.Empty, + }; + } + catch (Exception) + { + udpClient.Close(); + udpClient.Dispose(); + } + } + return null; + } + private int BindForward(UdpClient serverUdpClient, IPEndPoint server) + { + IPAddress localIP = NetworkHelper.IPv6Support ? IPAddress.IPv6Any : IPAddress.Any; + UdpClient udpClient = new UdpClient(localIP.AddressFamily); + udpClient.Client.WindowsUdpBug(); + + AsyncUserTokenForward token = new AsyncUserTokenForward { SourceUdpClient = udpClient, TargetUdpClient = serverUdpClient, TargetEP = server }; + IAsyncResult result = udpClient.BeginReceive(ReceiveCallbackUdpForward, token); + + return (udpClient.Client.LocalEndPoint as IPEndPoint).Port; + } + private void ReceiveCallbackUdpForward(IAsyncResult result) + { + AsyncUserTokenForward token = result.AsyncState as AsyncUserTokenForward; + try + { + byte[] bytes = token.SourceUdpClient.EndReceive(result, ref token.tempEP); + token.TargetUdpClient.Send(bytes, token.TargetEP); + + if (token.Received == false) + { + token.Received = true; + AsyncUserTokenForward token1 = new AsyncUserTokenForward { SourceUdpClient = token.TargetUdpClient, TargetUdpClient = token.SourceUdpClient, TargetEP = token.tempEP, Received = true }; + result = token.TargetUdpClient.BeginReceive(ReceiveCallbackUdpForward, token1); + } + + result = token.SourceUdpClient.BeginReceive(ReceiveCallbackUdpForward, token); + } + catch (Exception) + { + token.Clear(); + } + } + + + private void RemoveBind(int port, bool close) + { + if (udpListeners.TryRemove(port, out AsyncUserToken token)) + { + if (close) + { + token.Clear(); + } + } + + } + private void BindListen(IPEndPoint local, TunnelTransportInfo state, IPEndPoint targetEP) + { + IPAddress localIP = NetworkHelper.IPv6Support ? IPAddress.IPv6Any : IPAddress.Any; + UdpClient udpClient = new UdpClient(localIP.AddressFamily); + udpClient.Client.ReuseBind(new IPEndPoint(localIP, local.Port)); + udpClient.Client.WindowsUdpBug(); + + AsyncUserToken token = new AsyncUserToken { SourceUdpClient = udpClient, State = state, TargetEP = targetEP, Port = local.Port }; + IAsyncResult result = udpClient.BeginReceive(ReceiveCallbackUdp, token); + + udpListeners.AddOrUpdate(local.Port, token, (a, b) => token); + } + private void ReceiveCallbackUdp(IAsyncResult result) + { + AsyncUserToken token = result.AsyncState as AsyncUserToken; + try + { + byte[] bytes = token.SourceUdpClient.EndReceive(result, ref token.tempEP); + if (token.Received == false) + { + if (bytes.AsSpan().SequenceEqual(UdpEndBytes)) + { + token.Received = true; + } + else + { + token.SourceUdpClient.Send(bytes, token.tempEP); + } + } + else + { + if (token.TargetUdpClient == null) + { + token.TargetUdpClient = new UdpClient(); + token.TargetUdpClient.Client.WindowsUdpBug(); + token.TargetUdpClient.Send(bytes, token.TargetEP); + AsyncUserToken token1 = new AsyncUserToken { SourceUdpClient = token.TargetUdpClient, TargetUdpClient = token.SourceUdpClient, Received = true, TargetEP = token.tempEP, Port = token.Port }; + result = token1.SourceUdpClient.BeginReceive(ReceiveCallbackUdp, token1); + } + else + { + token.TargetUdpClient.Send(bytes, token.TargetEP); + } + } + + result = token.SourceUdpClient.BeginReceive(ReceiveCallbackUdp, token); + } + catch (Exception) + { + RemoveBind(token.Port, true); + } + } + private void BindAndTTL(TunnelTransportInfo tunnelTransportInfo) + { + //给对方发送TTL消息 + IPAddress[] localIps = tunnelTransportInfo.Remote.LocalIps.Where(c => c.Equals(tunnelTransportInfo.Remote.Local.Address) == false).ToArray(); + List eps = new List(); + foreach (IPAddress item in localIps) + { + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Local.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port)); + eps.Add(new IPEndPoint(item, tunnelTransportInfo.Remote.Remote.Port + 1)); + } + eps.AddRange(new List{ + new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port), + new IPEndPoint(tunnelTransportInfo.Remote.Remote.Address,tunnelTransportInfo.Remote.Remote.Port+1), + }); + foreach (var ip in eps.Where(c => NotIPv6Support(c.Address) == false)) + { + try + { + IPEndPoint ep = new IPEndPoint(ip.AddressFamily == AddressFamily.InterNetwork ? IPAddress.Any : IPAddress.IPv6Any, tunnelTransportInfo.Local.Local.Port); + using UdpClient udpClient = new UdpClient(ep.AddressFamily); + udpClient.Client.ReuseBind(ep); + udpClient.Send(new byte[] { 0 }); + } + catch (Exception ex) + { + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Error(ex); + } + } + } + } + + + private ConcurrentDictionary> reverseDic = new ConcurrentDictionary>(); + private async Task WaitReverse(TunnelTransportInfo tunnelTransportInfo) + { + TaskCompletionSource tcs = new TaskCompletionSource(); + reverseDic.TryAdd(tunnelTransportInfo.Remote.MachineName, tcs); + + try + { + ITunnelConnection connection = await tcs.Task.WaitAsync(TimeSpan.FromMilliseconds(5000)); + return connection; + } + catch (Exception) + { + } + finally + { + reverseDic.TryRemove(tunnelTransportInfo.Remote.MachineName, out _); + } + return null; + } + public void OnFail(TunnelTransportInfo tunnelTransportInfo) + { + RemoveBind(tunnelTransportInfo.Local.Local.Port, true); + if (reverseDic.TryRemove(tunnelTransportInfo.Remote.MachineName, out TaskCompletionSource tcs)) + { + tcs.SetResult(null); + } + } + public void OnSuccess(TunnelTransportInfo tunnelTransportInfo) + { + RemoveBind(tunnelTransportInfo.Local.Local.Port, false); + if (reverseDic.TryRemove(tunnelTransportInfo.Remote.MachineName, out TaskCompletionSource tcs)) + { + tcs.SetResult(null); + } + } + + + private async Task OnUdpConnected(TunnelTransportInfo state, QuicStream stream, IPEndPoint remoteEndpoint) + { + if (state.TransportName == Name) + { + try + { + TunnelConnectionQuic result = new TunnelConnectionQuic + { + RemoteMachineName = state.Remote.MachineName, + Direction = state.Direction, + ProtocolType = TunnelProtocolType.Quic, + Stream = stream, + Type = TunnelType.P2P, + Mode = TunnelMode.Server, + TransactionId = state.TransactionId, + TransportName = state.TransportName, + IPEndPoint = remoteEndpoint, + Label = string.Empty, + }; + if (reverseDic.TryRemove(state.Remote.MachineName, out TaskCompletionSource tcs)) + { + tcs.SetResult(result); + return; + } + OnConnected(result); + } + catch (Exception ex) + { + if (Logger.Instance.LoggerLevel <= LoggerTypes.DEBUG) + { + Logger.Instance.Error(ex); + } + } + } + await Task.CompletedTask; + } + + private bool NotIPv6Support(IPAddress ip) + { + return ip.AddressFamily == AddressFamily.InterNetworkV6 && (NetworkHelper.IPv6Support == false); + } + + public sealed class AsyncUserToken : AsyncUserTokenForward + { + public TunnelTransportInfo State { get; set; } + public int Port { get; set; } + } + + public class AsyncUserTokenForward + { + public UdpClient SourceUdpClient { get; set; } + public UdpClient TargetUdpClient { get; set; } + public IPEndPoint TargetEP { get; set; } + public IPEndPoint tempEP = new IPEndPoint(IPAddress.Any, 0); + + public bool Received { get; set; } + + public void Clear() + { + try + { + SourceUdpClient?.Close(); + TargetUdpClient?.Close(); + } + catch (Exception) + { + } + } + } + } +} diff --git a/common.libs/common.libs.csproj b/common.libs/common.libs.csproj index be25f29d..c6dbb31e 100644 --- a/common.libs/common.libs.csproj +++ b/common.libs/common.libs.csproj @@ -2,10 +2,11 @@ Library - net7.0;net8.0 + net8.0 false true Debug;Release;ReleaseNetwork;ReleaseMonitor + true DEBUG;TRACE @@ -27,21 +28,10 @@ none false - - embedded - + embedded - - embedded - - - embedded - - - embedded - embedded diff --git a/common.libs/extends/SocketExtends.cs b/common.libs/extends/SocketExtends.cs index bd9681f5..1bd756e9 100644 --- a/common.libs/extends/SocketExtends.cs +++ b/common.libs/extends/SocketExtends.cs @@ -51,7 +51,13 @@ namespace common.libs.extends } finally { - socket.Close(); + try + { + socket.Close(); + } + catch (Exception) + { + } } } }