diff --git a/.editorconfig b/.editorconfig index 1c59ad5..b8f1d20 100644 --- a/.editorconfig +++ b/.editorconfig @@ -50,7 +50,7 @@ csharp_new_line_before_members_in_object_initializers = true csharp_new_line_before_members_in_anonymous_types = true # Namespace settings -csharp_style_namespace_declarations = file_scoped +csharp_style_namespace_declarations = file_scoped:warning [*.{xml,config,*proj,nuspec,props,resx,targets,yml,tasks}] indent_size = 2 @@ -218,6 +218,29 @@ dotnet_style_qualification_for_field = false:suggestion dotnet_style_qualification_for_property = false:suggestion dotnet_style_qualification_for_method = false:silent dotnet_style_qualification_for_event = false:silent +dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_code_quality_unused_parameters = all:suggestion +dotnet_style_readonly_field = true:suggestion +dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent +dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent +dotnet_style_allow_statement_immediately_after_block_experimental = true:silent +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion [**/{test,samples,perf}/**.{cs,vb}] # CA1018: Mark attributes with AttributeUsageAttribute @@ -324,6 +347,21 @@ csharp_style_expression_bodied_accessors = true:silent csharp_style_expression_bodied_lambdas = true:silent csharp_style_expression_bodied_local_functions = false:silent csharp_style_conditional_delegate_call = true:suggestion +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_prefer_static_local_function = true:suggestion +csharp_style_prefer_readonly_struct = true:suggestion +csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent +csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent +csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent +csharp_style_prefer_switch_expression = true:suggestion +csharp_style_prefer_pattern_matching = true:silent +csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion +csharp_style_pattern_matching_over_as_with_null_check = true:suggestion +csharp_style_prefer_not_pattern = true:suggestion +csharp_style_prefer_extended_property_pattern = true:suggestion [*.vb] #### ʽ #### diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml index 1c8ce2b..3eca09b 100644 --- a/.github/workflows/dotnetcore.yml +++ b/.github/workflows/dotnetcore.yml @@ -3,11 +3,11 @@ name: Build on: push: branches: - - master + - v2 env: # 设置 docker 镜像名 - IMAGE_NAME: fasttunnel + IMAGE_NAME: fasttunnel-v2 jobs: build: @@ -18,8 +18,9 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 7.0.* + include-prerelease: true - name: Build with dotnet - run: chmod +x ./publish-win.sh && ./publish-win.sh + run: chmod +x ./publish.sh publish: name: publish-core @@ -31,7 +32,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 7.0.* - + include-prerelease: true # Publish - name: publish on version change id: publish_nuget @@ -77,6 +78,7 @@ jobs: uses: actions/setup-dotnet@v1 with: dotnet-version: 7.0.* + include-prerelease: true # Publish - name: publish on version change @@ -102,11 +104,20 @@ jobs: run: | IMAGE_ID=springhgui/$IMAGE_NAME IMAGE_ID=$(echo $IMAGE_ID | tr '[A-Z]' '[a-z]') - VERSION=latest + VERSION=$(date "+%Y.%m.%d") echo IMAGE_ID=$IMAGE_ID echo VERSION=$VERSION # 设置镜像 id 和版本号 + echo [tag] $IMAGE_NAME $IMAGE_ID:$VERSION docker tag $IMAGE_NAME $IMAGE_ID:$VERSION # 进行 push + echo [push] $IMAGE_ID:$VERSION docker push $IMAGE_ID:$VERSION + # 再上传一份覆盖latest + echo [tag] $IMAGE_ID:$VERSION $IMAGE_ID:latest + docker tag $IMAGE_ID:$VERSION $IMAGE_ID:latest + + echo [push] $IMAGE_ID:latest + docker push $IMAGE_ID:latest + diff --git a/FastTunnel.Api/FastTunnel.Api.csproj b/FastTunnel.Api/FastTunnel.Api.csproj index b59cfa9..827c384 100644 --- a/FastTunnel.Api/FastTunnel.Api.csproj +++ b/FastTunnel.Api/FastTunnel.Api.csproj @@ -1,6 +1,6 @@ - net6.0 + net7.0 1.1.0 https://github.com/FastTunnel/FastTunnel/tree/v2/FastTunnel.Api https://github.com/FastTunnel/FastTunnel/tree/v2/FastTunnel.Api diff --git a/FastTunnel.Client/FastTunnel.Client.csproj b/FastTunnel.Client/FastTunnel.Client.csproj index 71f2df0..a9d0752 100644 --- a/FastTunnel.Client/FastTunnel.Client.csproj +++ b/FastTunnel.Client/FastTunnel.Client.csproj @@ -1,6 +1,6 @@ - net6.0 + net7.0 Exe @@ -13,7 +13,7 @@ - + diff --git a/FastTunnel.Client/Program.cs b/FastTunnel.Client/Program.cs index 4e8dff1..cf20d95 100644 --- a/FastTunnel.Client/Program.cs +++ b/FastTunnel.Client/Program.cs @@ -7,10 +7,9 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using System; -using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.Configuration; using Serilog; -using FastTunnel.Core.Extensions; +using FastTunnel.Core.Client.Extensions; namespace FastTunnel.Client; diff --git a/FastTunnel.Core/Config/DefaultClientConfig.cs b/FastTunnel.Core.Client/DefaultClientConfig.cs similarity index 85% rename from FastTunnel.Core/Config/DefaultClientConfig.cs rename to FastTunnel.Core.Client/DefaultClientConfig.cs index c4b8022..5f7afa3 100644 --- a/FastTunnel.Core/Config/DefaultClientConfig.cs +++ b/FastTunnel.Core.Client/DefaultClientConfig.cs @@ -1,9 +1,10 @@ -// Licensed under the Apache License, Version 2.0 (the "License"). +// Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // You may obtain a copy of the License at // https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE // Copyright (c) 2019 Gui.H +using FastTunnel.Core.Client; using FastTunnel.Core.Models; using System.Collections.Generic; @@ -19,4 +20,4 @@ namespace FastTunnel.Core.Config public IEnumerable Forwards { get; set; } } -} \ No newline at end of file +} diff --git a/FastTunnel.Core.Client/Extensions/ServicesExtensions.cs b/FastTunnel.Core.Client/Extensions/ServicesExtensions.cs new file mode 100644 index 0000000..84f15f1 --- /dev/null +++ b/FastTunnel.Core.Client/Extensions/ServicesExtensions.cs @@ -0,0 +1,33 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Core.Config; +using FastTunnel.Core.Handlers.Client; +using FastTunnel.Core.Services; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; + +namespace FastTunnel.Core.Client.Extensions +{ + public static class ServicesExtensions + { + /// + /// 客户端依赖及HostedService + /// + /// + public static void AddFastTunnelClient(this IServiceCollection services, IConfigurationSection configurationSection) + { + services.Configure(configurationSection); + + services.AddTransient() + .AddSingleton() + .AddSingleton(); + + services.AddHostedService(); + } + + } +} diff --git a/FastTunnel.Core/Extensions/SocketExtensions.cs b/FastTunnel.Core.Client/Extensions/SocketExtensions.cs similarity index 100% rename from FastTunnel.Core/Extensions/SocketExtensions.cs rename to FastTunnel.Core.Client/Extensions/SocketExtensions.cs diff --git a/FastTunnel.Core.Client/FastTunnel.Core.Client.csproj b/FastTunnel.Core.Client/FastTunnel.Core.Client.csproj new file mode 100644 index 0000000..39b4643 --- /dev/null +++ b/FastTunnel.Core.Client/FastTunnel.Core.Client.csproj @@ -0,0 +1,39 @@ + + + + 2.1.1 + netcoreapp3.1;net5.0;net6.0;net7.0; + enable + preview + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FastTunnel.Core/Client/FastTunnelClient.cs b/FastTunnel.Core.Client/FastTunnelClient.cs similarity index 97% rename from FastTunnel.Core/Client/FastTunnelClient.cs rename to FastTunnel.Core.Client/FastTunnelClient.cs index b1ce625..0f94b99 100644 --- a/FastTunnel.Core/Client/FastTunnelClient.cs +++ b/FastTunnel.Core.Client/FastTunnelClient.cs @@ -17,7 +17,6 @@ using FastTunnel.Core.Handlers.Client; using FastTunnel.Core.Models; using FastTunnel.Core.Models.Massage; using FastTunnel.Core.Utilitys; -using Microsoft.AspNetCore.DataProtection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; @@ -141,7 +140,13 @@ public class FastTunnelClient : IFastTunnelClient throw new Exception($"未处理的消息:cmd={cmd}"); } +#if NETCOREAPP3_1 + var content = Encoding.UTF8.GetString(line.Slice(1).ToArray()); +#endif + +#if NET5_0_OR_GREATER var content = Encoding.UTF8.GetString(line.Slice(1)); +#endif handler.HandlerMsgAsync(this, content, cancellationToken); } catch (Exception ex) diff --git a/FastTunnel.Core/Handlers/Client/IClientHandler.cs b/FastTunnel.Core.Client/Handlers/IClientHandler.cs similarity index 99% rename from FastTunnel.Core/Handlers/Client/IClientHandler.cs rename to FastTunnel.Core.Client/Handlers/IClientHandler.cs index 51916f9..b3de45f 100644 --- a/FastTunnel.Core/Handlers/Client/IClientHandler.cs +++ b/FastTunnel.Core.Client/Handlers/IClientHandler.cs @@ -26,4 +26,5 @@ namespace FastTunnel.Core.Handlers.Client /// Task HandlerMsgAsync(FastTunnelClient cleint, string msg, CancellationToken cancellationToken); } + } diff --git a/FastTunnel.Core/Handlers/Client/LogHandler.cs b/FastTunnel.Core.Client/Handlers/LogHandler.cs similarity index 100% rename from FastTunnel.Core/Handlers/Client/LogHandler.cs rename to FastTunnel.Core.Client/Handlers/LogHandler.cs diff --git a/FastTunnel.Core/Handlers/Client/SwapHandler.cs b/FastTunnel.Core.Client/Handlers/SwapHandler.cs similarity index 92% rename from FastTunnel.Core/Handlers/Client/SwapHandler.cs rename to FastTunnel.Core.Client/Handlers/SwapHandler.cs index 4e078f2..5c77af0 100644 --- a/FastTunnel.Core/Handlers/Client/SwapHandler.cs +++ b/FastTunnel.Core.Client/Handlers/SwapHandler.cs @@ -9,15 +9,16 @@ using System.Net.Sockets; using System.Text; using System.Threading; using System.Threading.Tasks; -using FastTunnel.Core.Sockets; using Microsoft.Extensions.Logging; using System.Net.Security; +using FastTunnel.Core.Client.Sockets; namespace FastTunnel.Core.Handlers.Client { public class SwapHandler : IClientHandler { readonly ILogger _logger; + static int connectionCount; public SwapHandler(ILogger logger) { @@ -37,6 +38,7 @@ namespace FastTunnel.Core.Handlers.Client { try { + Interlocked.Increment(ref connectionCount); _logger.LogDebug($"======Swap {requestId} Start======"); using (Stream serverStream = await createRemote(requestId, cleint, cancellationToken)) using (Stream localStream = await createLocal(requestId, address, cancellationToken)) @@ -53,7 +55,9 @@ namespace FastTunnel.Core.Handlers.Client } finally { + Interlocked.Decrement(ref connectionCount); _logger.LogDebug($"======Swap {requestId} End======"); + _logger.LogDebug($"统计SwapHandler连接数:{connectionCount}"); } } diff --git a/FastTunnel.Core/Config/IClientConfig.cs b/FastTunnel.Core.Client/IClientConfig.cs similarity index 63% rename from FastTunnel.Core/Config/IClientConfig.cs rename to FastTunnel.Core.Client/IClientConfig.cs index 79010ac..8f4256e 100644 --- a/FastTunnel.Core/Config/IClientConfig.cs +++ b/FastTunnel.Core.Client/IClientConfig.cs @@ -1,13 +1,18 @@ -// Licensed under the Apache License, Version 2.0 (the "License"). +// Licensed under the Apache License, Version 2.0 (the "License"). // You may not use this file except in compliance with the License. // You may obtain a copy of the License at // https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE // Copyright (c) 2019 Gui.H -using FastTunnel.Core.Models; +using System; using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FastTunnel.Core.Config; +using FastTunnel.Core.Models; -namespace FastTunnel.Core.Config +namespace FastTunnel.Core.Client { public interface IClientConfig { @@ -18,12 +23,5 @@ namespace FastTunnel.Core.Config public IEnumerable Forwards { get; set; } } - public class SuiDaoServer - { - public string Protocol { get; set; } = "ws"; - - public string ServerAddr { get; set; } - - public int ServerPort { get; set; } - } } + diff --git a/FastTunnel.Core/Client/IFastTunnelClient.cs b/FastTunnel.Core.Client/IFastTunnelClient.cs similarity index 100% rename from FastTunnel.Core/Client/IFastTunnelClient.cs rename to FastTunnel.Core.Client/IFastTunnelClient.cs diff --git a/FastTunnel.Core.Client/Models/SuiDaoServer.cs b/FastTunnel.Core.Client/Models/SuiDaoServer.cs new file mode 100644 index 0000000..a1344be --- /dev/null +++ b/FastTunnel.Core.Client/Models/SuiDaoServer.cs @@ -0,0 +1,20 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using FastTunnel.Core.Models; +using System.Collections.Generic; + +namespace FastTunnel.Core.Config +{ + public class SuiDaoServer + { + public string Protocol { get; set; } = "ws"; + + public string ServerAddr { get; set; } + + public int ServerPort { get; set; } + } +} diff --git a/FastTunnel.Core/Services/ServiceFastTunnelClient.cs b/FastTunnel.Core.Client/Services/ServiceFastTunnelClient.cs similarity index 96% rename from FastTunnel.Core/Services/ServiceFastTunnelClient.cs rename to FastTunnel.Core.Client/Services/ServiceFastTunnelClient.cs index 52ad9e9..496602d 100644 --- a/FastTunnel.Core/Services/ServiceFastTunnelClient.cs +++ b/FastTunnel.Core.Client/Services/ServiceFastTunnelClient.cs @@ -4,9 +4,7 @@ // https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE // Copyright (c) 2019 Gui.H -using FastTunnel.Core.Config; using FastTunnel.Core.Client; -using FastTunnel.Core.Models; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; diff --git a/FastTunnel.Core/Sockets/DnsSocketFactory.cs b/FastTunnel.Core.Client/Sockets/DnsSocketFactory.cs similarity index 88% rename from FastTunnel.Core/Sockets/DnsSocketFactory.cs rename to FastTunnel.Core.Client/Sockets/DnsSocketFactory.cs index c64cce0..e85f257 100644 --- a/FastTunnel.Core/Sockets/DnsSocketFactory.cs +++ b/FastTunnel.Core.Client/Sockets/DnsSocketFactory.cs @@ -13,16 +13,18 @@ using System.Net.Sockets; using System.Text; using System.Threading.Tasks; -namespace FastTunnel.Core.Sockets +namespace FastTunnel.Core.Client.Sockets { + public class DnsSocketFactory { public static async Task ConnectAsync(string host, int port) { var Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); - DnsEndPoint dnsEndPoint = new DnsEndPoint(host, port); + var dnsEndPoint = new DnsEndPoint(host, port); await Socket.ConnectAsync(dnsEndPoint); return Socket; } } + } diff --git a/FastTunnel.Core/Utilitys/AssemblyUtility.cs b/FastTunnel.Core.Client/Utilitys/AssemblyUtility.cs similarity index 100% rename from FastTunnel.Core/Utilitys/AssemblyUtility.cs rename to FastTunnel.Core.Client/Utilitys/AssemblyUtility.cs diff --git a/FastTunnel.Core/Client/FastTunnelServer.cs b/FastTunnel.Core/Client/FastTunnelServer.cs index ebb5184..2ac3b29 100644 --- a/FastTunnel.Core/Client/FastTunnelServer.cs +++ b/FastTunnel.Core/Client/FastTunnelServer.cs @@ -15,6 +15,7 @@ using Microsoft.Extensions.Options; using System.IO; using Yarp.ReverseProxy.Configuration; using System.Collections.Generic; +using FastTunnel.Core.Forwarder.MiddleWare; namespace FastTunnel.Core.Client { @@ -51,7 +52,7 @@ namespace FastTunnel.Core.Client internal void ClientLogin(TunnelClient client) { Interlocked.Increment(ref ConnectedClientCount); - logger.LogInformation($"客户端连接 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount}"); + logger.LogInformation($"客户端连接 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount}"); Clients.Add(client); } @@ -63,7 +64,7 @@ namespace FastTunnel.Core.Client internal void ClientLogout(TunnelClient client) { Interlocked.Decrement(ref ConnectedClientCount); - logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount}"); + logger.LogInformation($"客户端关闭 {client.RemoteIpAddress} 当前在线数:{ConnectedClientCount},统计CLIENT连接数:{FastTunnelClientHandler.ConnectionCount}"); Clients.Remove(client); client.Logout(); } diff --git a/FastTunnel.Core/Extensions/ObjectExtensions.cs b/FastTunnel.Core/Extensions/ObjectExtensions.cs index 5c17e0e..12aab42 100644 --- a/FastTunnel.Core/Extensions/ObjectExtensions.cs +++ b/FastTunnel.Core/Extensions/ObjectExtensions.cs @@ -3,13 +3,17 @@ // You may obtain a copy of the License at // https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE // Copyright (c) 2019 Gui.H - +#if NETCOREAPP3_1_OR_GREATER using System.Text.Json; +#else + +#endif namespace FastTunnel.Core.Extensions { public static class ObjectExtensions { +#if NETCOREAPP3_1_OR_GREATER public static string ToJson(this object message) { if (message == null) @@ -20,5 +24,17 @@ namespace FastTunnel.Core.Extensions var jsonOptions = new JsonSerializerOptions { WriteIndented = false }; return JsonSerializer.Serialize(message, message.GetType(), jsonOptions); } +#else + public static string ToJson(this object message) + { + if (message == null) + { + return null; + } + + var jsonOptions = new JsonSerializerOptions { WriteIndented = false }; + return JsonSerializer.Serialize(message, message.GetType(), jsonOptions); + } +#endif } } diff --git a/FastTunnel.Core/Extensions/ServicesExtensions.cs b/FastTunnel.Core/Extensions/ServicesExtensions.cs index 2c13031..65f80c2 100644 --- a/FastTunnel.Core/Extensions/ServicesExtensions.cs +++ b/FastTunnel.Core/Extensions/ServicesExtensions.cs @@ -11,9 +11,7 @@ using FastTunnel.Core.Config; using FastTunnel.Core.Filters; using FastTunnel.Core.Forwarder; using FastTunnel.Core.Forwarder.MiddleWare; -using FastTunnel.Core.Handlers.Client; using FastTunnel.Core.Handlers.Server; -using FastTunnel.Core.Services; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; @@ -27,25 +25,6 @@ namespace FastTunnel.Core.Extensions; public static class ServicesExtensions { - /// - /// 客户端依赖及HostedService - /// - /// - public static void AddFastTunnelClient(this IServiceCollection services, IConfigurationSection configurationSection) - { - services.Configure(configurationSection); - services.AddFastTunnelClient(); - } - - public static void AddFastTunnelClient(this IServiceCollection services) - { - services.AddTransient() - .AddSingleton() - .AddSingleton() - .AddSingleton(); - - services.AddHostedService(); - } /// /// 添加服务端后台进程 diff --git a/FastTunnel.Core/FastTunnel.Core.csproj b/FastTunnel.Core/FastTunnel.Core.csproj index edbc1a6..0da9f0e 100644 --- a/FastTunnel.Core/FastTunnel.Core.csproj +++ b/FastTunnel.Core/FastTunnel.Core.csproj @@ -1,7 +1,7 @@ - net6.0 + net6.0;net7.0 2.1.1 https://github.com/SpringHgui/FastTunnel MIT @@ -17,6 +17,7 @@ FastTunnel.Core true FastTunnel.Core + Preview diff --git a/FastTunnel.Core/Forwarder/FastTunnelForwarderHttpClientFactory.cs b/FastTunnel.Core/Forwarder/FastTunnelForwarderHttpClientFactory.cs index 06b9209..dbef3f1 100644 --- a/FastTunnel.Core/Forwarder/FastTunnelForwarderHttpClientFactory.cs +++ b/FastTunnel.Core/Forwarder/FastTunnelForwarderHttpClientFactory.cs @@ -1,11 +1,12 @@ -// Copyright (c) 2019-2022 Gui.H. https://github.com/FastTunnel/FastTunnel -// The FastTunnel licenses this file to you under the Apache License Version 2.0. -// For more details,You may obtain License file at: https://github.com/FastTunnel/FastTunnel/blob/v2/LICENSE +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H using FastTunnel.Core.Client; using FastTunnel.Core.Extensions; using FastTunnel.Core.Models; -using FastTunnel.Core.Sockets; using Microsoft.AspNetCore.Connections.Features; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; @@ -22,100 +23,106 @@ using System.Threading.Tasks; using System.Xml.Linq; using Yarp.ReverseProxy.Forwarder; -namespace FastTunnel.Core.Forwarder +namespace FastTunnel.Core.Forwarder; + +public class FastTunnelForwarderHttpClientFactory : ForwarderHttpClientFactory { - public class FastTunnelForwarderHttpClientFactory : ForwarderHttpClientFactory + readonly ILogger logger; + readonly FastTunnelServer fastTunnelServer; + private readonly IHttpContextAccessor _httpContextAccessor; + static int connectionCount; + + public FastTunnelForwarderHttpClientFactory( + ILogger logger, + IHttpContextAccessor httpContextAccessor, FastTunnelServer fastTunnelServer) { - readonly ILogger logger; - readonly FastTunnelServer fastTunnelServer; - private readonly IHttpContextAccessor _httpContextAccessor; + this.fastTunnelServer = fastTunnelServer; + this.logger = logger; + _httpContextAccessor = httpContextAccessor; + } - public FastTunnelForwarderHttpClientFactory( - ILogger logger, - IHttpContextAccessor httpContextAccessor, FastTunnelServer fastTunnelServer) + protected override void ConfigureHandler(ForwarderHttpClientContext context, SocketsHttpHandler handler) + { + base.ConfigureHandler(context, handler); + handler.ConnectCallback = ConnectCallback; + } + + private async ValueTask ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken) + { + var host = context.InitialRequestMessage.RequestUri.Host; + + var contextRequest = _httpContextAccessor.HttpContext; + //var lifetime = contextRequest.Features.Get()!; + + try { - this.fastTunnelServer = fastTunnelServer; - this.logger = logger; - _httpContextAccessor = httpContextAccessor; + Interlocked.Increment(ref connectionCount); + var res = await proxyAsync(host, context, contextRequest.RequestAborted); + return res; } - - protected override void ConfigureHandler(ForwarderHttpClientContext context, SocketsHttpHandler handler) + catch (Exception) { - base.ConfigureHandler(context, handler); - handler.ConnectCallback = ConnectCallback; + throw; } - - private async ValueTask ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken) + finally { - var host = context.InitialRequestMessage.RequestUri.Host; - - var contextRequest = _httpContextAccessor.HttpContext; - //var lifetime = contextRequest.Features.Get()!; - - try - { - var res = await proxyAsync(host, context, contextRequest.RequestAborted); - return res; - } - catch (Exception) - { - throw; - } - } - - public async ValueTask proxyAsync(string host, SocketsHttpConnectionContext context, CancellationToken cancellation) - { - WebInfo web; - if (!fastTunnelServer.WebList.TryGetValue(host, out web)) - { - // 客户端已离线 - return await OfflinePage(host, context); - } - - var msgId = Guid.NewGuid().ToString().Replace("-", ""); - - TaskCompletionSource tcs = new(); - logger.LogDebug($"[Http]Swap开始 {msgId}|{host}=>{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}"); - - cancellation.Register(() => - { - logger.LogDebug($"[Proxy TimeOut]:{msgId}"); - tcs.TrySetCanceled(); - }); - - fastTunnelServer.ResponseTasks.TryAdd(msgId, (tcs, cancellation)); - - try - { - // 发送指令给客户端,等待建立隧道 - await web.Socket.SendCmdAsync(MessageType.SwapMsg, $"{msgId}|{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}", cancellation); - var res = await tcs.Task.WaitAsync(cancellation); - - logger.LogDebug($"[Http]Swap OK {msgId}"); - return res; - } - catch (WebSocketException) - { - // 通讯异常,返回客户端离线 - return await OfflinePage(host, context); - } - catch (Exception) - { - throw; - } - finally - { - fastTunnelServer.ResponseTasks.TryRemove(msgId, out _); - } - } - - - private async ValueTask OfflinePage(string host, SocketsHttpConnectionContext context) - { - var bytes = Encoding.UTF8.GetBytes( - $"HTTP/1.1 200 OK\r\nContent-Type:text/html; charset=utf-8\r\n\r\n{TunnelResource.Page_Offline}\r\n"); - - return await Task.FromResult(new ResponseStream(bytes)); + Interlocked.Decrement(ref connectionCount); + logger.LogDebug($"统计YARP连接数:{connectionCount}"); } } + + public async ValueTask proxyAsync(string host, SocketsHttpConnectionContext context, CancellationToken cancellation) + { + WebInfo web; + if (!fastTunnelServer.WebList.TryGetValue(host, out web)) + { + // 客户端已离线 + return await OfflinePage(host, context); + } + + var msgId = Guid.NewGuid().ToString().Replace("-", ""); + + TaskCompletionSource tcs = new(); + logger.LogDebug($"[Http]Swap开始 {msgId}|{host}=>{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}"); + + cancellation.Register(() => + { + logger.LogDebug($"[Proxy TimeOut]:{msgId}"); + tcs.TrySetCanceled(); + }); + + fastTunnelServer.ResponseTasks.TryAdd(msgId, (tcs, cancellation)); + + try + { + // 发送指令给客户端,等待建立隧道 + await web.Socket.SendCmdAsync(MessageType.SwapMsg, $"{msgId}|{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}", cancellation); + var res = await tcs.Task.WaitAsync(cancellation); + + logger.LogDebug($"[Http]Swap OK {msgId}"); + return res; + } + catch (WebSocketException) + { + // 通讯异常,返回客户端离线 + return await OfflinePage(host, context); + } + catch (Exception) + { + throw; + } + finally + { + fastTunnelServer.ResponseTasks.TryRemove(msgId, out _); + } + } + + + private async ValueTask OfflinePage(string host, SocketsHttpConnectionContext context) + { + var bytes = Encoding.UTF8.GetBytes( + $"HTTP/1.1 200 OK\r\nContent-Type:text/html; charset=utf-8\r\n\r\n{TunnelResource.Page_Offline}\r\n"); + + return await Task.FromResult(new ResponseStream(bytes)); + } } diff --git a/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelClientHandler.cs b/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelClientHandler.cs index 85711e9..8ee32db 100644 --- a/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelClientHandler.cs +++ b/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelClientHandler.cs @@ -24,6 +24,10 @@ namespace FastTunnel.Core.Forwarder.MiddleWare readonly Version serverVersion; readonly ILoginHandler loginHandler; + static int connectionCount; + + public static int ConnectionCount => connectionCount; + public FastTunnelClientHandler( ILogger logger, FastTunnelServer fastTunnelServer, ILoginHandler loginHandler) { @@ -44,7 +48,16 @@ namespace FastTunnel.Core.Forwarder.MiddleWare return; }; - await handleClient(context, version); + Interlocked.Increment(ref connectionCount); + + try + { + await handleClient(context, version); + } + finally + { + Interlocked.Decrement(ref connectionCount); + } } catch (Exception ex) { diff --git a/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelSwapHandler.cs b/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelSwapHandler.cs index 9920118..b9fcca6 100644 --- a/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelSwapHandler.cs +++ b/FastTunnel.Core/Forwarder/MiddleWare/FastTunnelSwapHandler.cs @@ -16,6 +16,9 @@ namespace FastTunnel.Core.Forwarder.MiddleWare { ILogger logger; FastTunnelServer fastTunnelServer; + static int connectionCount; + + public static int ConnectionCount => connectionCount; public FastTunnelSwapHandler(ILogger logger, FastTunnelServer fastTunnelServer) { @@ -25,6 +28,8 @@ namespace FastTunnel.Core.Forwarder.MiddleWare public async Task Handle(HttpContext context, Func next) { + Interlocked.Increment(ref connectionCount); + try { if (context.Request.Method != "PROXY") @@ -73,10 +78,16 @@ namespace FastTunnel.Core.Forwarder.MiddleWare await closedAwaiter.Task.WaitAsync(cts.Token); logger.LogDebug($"[PROXY]:Closed {requestId}"); } + catch (TaskCanceledException) { } catch (Exception ex) { logger.LogError(ex); } + finally + { + Interlocked.Decrement(ref connectionCount); + logger.LogDebug($"统计SWAP连接数:{ConnectionCount}"); + } } } } diff --git a/FastTunnel.Core/Handlers/Server/ForwardDispatcher.cs b/FastTunnel.Core/Handlers/Server/ForwardDispatcher.cs index 01d9263..e32ea52 100644 --- a/FastTunnel.Core/Handlers/Server/ForwardDispatcher.cs +++ b/FastTunnel.Core/Handlers/Server/ForwardDispatcher.cs @@ -9,7 +9,6 @@ using FastTunnel.Core.Exceptions; using FastTunnel.Core.Extensions; using FastTunnel.Core.Listener; using FastTunnel.Core.Models; -using FastTunnel.Core.Sockets; using Microsoft.AspNetCore.Hosting.Server; using Microsoft.Extensions.Logging; using System; diff --git a/FastTunnel.Core/Listener/PortProxyListenerV2.cs b/FastTunnel.Core/Listener/PortProxyListenerV2.cs new file mode 100644 index 0000000..b19076d --- /dev/null +++ b/FastTunnel.Core/Listener/PortProxyListenerV2.cs @@ -0,0 +1,106 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using BeetleX; +using BeetleX.EventArgs; +using FastTunnel.Core.Handlers.Server; +using Microsoft.AspNetCore.Hosting.Server; +using Microsoft.Extensions.Logging; +using System; +using System.Net; +using System.Net.Sockets; +using System.Net.WebSockets; +using System.Threading; +using IServer = BeetleX.IServer; + +namespace FastTunnel.Core.Listener +{ + public class PortProxyListenerV2 + { + ILogger _logerr; + + public string ListenIp { get; private set; } + + public int ListenPort { get; private set; } + + public IServer Server { get; set; } + + int m_numConnectedSockets; + + private IServer server; + + bool shutdown; + ForwardDispatcher _requestDispatcher; + WebSocket client; + + // string ip, int port, ILogger logerr, WebSocket client + public PortProxyListenerV2() + { + //IPAddress ipa = IPAddress.Parse(ListenIp); + //IPEndPoint localEndPoint = new IPEndPoint(ipa, ListenPort); + } + + public void Start(ForwardDispatcher requestDispatcher, string host, int port, ILogger logger, WebSocket webSocket) + { + this.client = webSocket; + this._logerr = logger; + this.ListenIp = host; + this.ListenPort = port; + + shutdown = false; + _requestDispatcher = requestDispatcher; + + server = SocketFactory.CreateTcpServer(); + var handler = server.Handler as TcpServerHandler; + handler.Sethanler(this); + server.Options.DefaultListen.Port = port; + server.Options.DefaultListen.Host = host; + server.Open(); + } + + //protected override void OnReceiveMessage(IServer server, ISession session, object message) + //{ + // base.OnReceiveMessage(server, session, message); + //} + + private void ProcessAcceptAsync(SocketAsyncEventArgs e) + { + // 将此客户端交由Dispatcher进行管理 + } + + internal async void Process(SessionReceiveEventArgs e) + { + //var pipeStream = e.Session.Stream.ToPipeStream(); + //if (pipeStream.TryReadLine(out string name)) + //{ + // Console.WriteLine(name); + // e.Stream.ToPipeStream().WriteLine("hello " + name); + // e.Stream.Flush(); + //} + + await _requestDispatcher.DispatchAsync(e.Stream, client); + } + + public void Stop() + { + if (shutdown) + return; + + try + { + server.Dispose(); + } + catch (Exception) + { + } + finally + { + + } + } + + } +} diff --git a/FastTunnel.Core/Listener/TcpServerHandler.cs b/FastTunnel.Core/Listener/TcpServerHandler.cs new file mode 100644 index 0000000..6233c33 --- /dev/null +++ b/FastTunnel.Core/Listener/TcpServerHandler.cs @@ -0,0 +1,67 @@ +// Licensed under the Apache License, Version 2.0 (the "License"). +// You may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// https://github.com/FastTunnel/FastTunnel/edit/v2/LICENSE +// Copyright (c) 2019 Gui.H + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using BeetleX; +using BeetleX.EventArgs; + +namespace FastTunnel.Core.Listener; + +internal class TcpServerHandler : ServerHandlerBase +{ + PortProxyListenerV2 proxyListenerV2; + + public override void Connected(IServer server, ConnectedEventArgs e) + { + Console.WriteLine("[Connected]"); + } + + public override void Connecting(IServer server, ConnectingEventArgs e) + { + Console.WriteLine("[Connecting]"); + } + + public override void Disconnect(IServer server, SessionEventArgs e) + { + Console.WriteLine("[Disconnect]"); + } + + public override void Error(IServer server, ServerErrorEventArgs e) + { + Console.WriteLine("[Error]"); + } + + public override void Opened(IServer server) + { + Console.WriteLine("[Opened]"); + } + + public override void SessionDetection(IServer server, SessionDetectionEventArgs e) + { + Console.WriteLine("[SessionDetection]"); + } + + public override void SessionPacketDecodeCompleted(IServer server, PacketDecodeCompletedEventArgs e) + { + Console.WriteLine("[SessionPacketDecodeCompleted]"); + } + + public override void SessionReceive(IServer server, SessionReceiveEventArgs e) + { + Console.WriteLine("[SessionReceive]"); + + proxyListenerV2.Process(e); + } + + internal void Sethanler(PortProxyListenerV2 portProxyListenerV2) + { + this.proxyListenerV2 = portProxyListenerV2; + } +} diff --git a/FastTunnel.Core/Models/ForwardConfig.cs b/FastTunnel.Core/Models/ForwardConfig.cs index e4af5bd..e93404d 100644 --- a/FastTunnel.Core/Models/ForwardConfig.cs +++ b/FastTunnel.Core/Models/ForwardConfig.cs @@ -22,7 +22,7 @@ namespace FastTunnel.Core.Models /// 服务端监听的端口号 1~65535 /// public int RemotePort { get; set; } - + /// /// 协议,内网服务监听的协议 /// diff --git a/FastTunnel.Core/Models/Massage/LogInMassage.cs b/FastTunnel.Core/Models/LogInMassage.cs similarity index 100% rename from FastTunnel.Core/Models/Massage/LogInMassage.cs rename to FastTunnel.Core/Models/LogInMassage.cs diff --git a/FastTunnel.Core/Models/Massage/TunnelMassage.cs b/FastTunnel.Core/Models/TunnelMassage.cs similarity index 100% rename from FastTunnel.Core/Models/Massage/TunnelMassage.cs rename to FastTunnel.Core/Models/TunnelMassage.cs diff --git a/FastTunnel.Core/Utilitys/WebSocketUtility.cs b/FastTunnel.Core/Utilitys/WebSocketUtility.cs index 89e599e..53c0fa9 100644 --- a/FastTunnel.Core/Utilitys/WebSocketUtility.cs +++ b/FastTunnel.Core/Utilitys/WebSocketUtility.cs @@ -12,121 +12,124 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; -namespace FastTunnel.Core.Utilitys; - -public class WebSocketUtility +namespace FastTunnel.Core.Utilitys { - private readonly WebSocket webSocket; - public WebSocketUtility(WebSocket webSocket, Action, CancellationToken> processLine) + + public class WebSocketUtility { - this.webSocket = webSocket; - ProcessLine = processLine; - } + private readonly WebSocket webSocket; - public Action, CancellationToken> ProcessLine { get; } - - public async Task ProcessLinesAsync(CancellationToken cancellationToken) - { - var pipe = new Pipe(); - var writing = FillPipeAsync(webSocket, pipe.Writer, cancellationToken); - var reading = ReadPipeAsync(pipe.Reader, cancellationToken); - - await Task.WhenAll(reading, writing); - } - - /// - /// 读取socket收到的消息写入Pipe - /// - /// - /// - /// - /// - /// - private async Task FillPipeAsync(WebSocket socket, PipeWriter writer, CancellationToken cancellationToken) - { - const int minimumBufferSize = 512; - - while (true) + public WebSocketUtility(WebSocket webSocket, Action, CancellationToken> processLine) { - // Allocate at least 512 bytes from the PipeWriter. - var memory = writer.GetMemory(minimumBufferSize); + this.webSocket = webSocket; + ProcessLine = processLine; + } - try + public Action, CancellationToken> ProcessLine { get; } + + public async Task ProcessLinesAsync(CancellationToken cancellationToken) + { + var pipe = new Pipe(); + var writing = FillPipeAsync(webSocket, pipe.Writer, cancellationToken); + var reading = ReadPipeAsync(pipe.Reader, cancellationToken); + + await Task.WhenAll(reading, writing); + } + + /// + /// 读取socket收到的消息写入Pipe + /// + /// + /// + /// + /// + /// + private async Task FillPipeAsync(WebSocket socket, PipeWriter writer, CancellationToken cancellationToken) + { + const int minimumBufferSize = 512; + + while (true) { - var bytesRead = await socket.ReceiveAsync(memory, cancellationToken); - if (bytesRead.Count == 0 || bytesRead.EndOfMessage || bytesRead.MessageType == WebSocketMessageType.Close) + // Allocate at least 512 bytes from the PipeWriter. + var memory = writer.GetMemory(minimumBufferSize); + + try + { + var bytesRead = await socket.ReceiveAsync(memory, cancellationToken); + if (bytesRead.Count == 0 || bytesRead.EndOfMessage || bytesRead.MessageType == WebSocketMessageType.Close) + { + break; + } + // Tell the PipeWriter how much was read from the Socket. + writer.Advance(bytesRead.Count); + } + catch (Exception) + { + break; + } + + // Make the data available to the PipeReader. + var result = await writer.FlushAsync(cancellationToken); + + if (result.IsCompleted) { break; } - // Tell the PipeWriter how much was read from the Socket. - writer.Advance(bytesRead.Count); - } - catch (Exception) - { - break; } - // Make the data available to the PipeReader. - var result = await writer.FlushAsync(cancellationToken); - - if (result.IsCompleted) - { - break; - } + // By completing PipeWriter, tell the PipeReader that there's no more data coming. + await writer.CompleteAsync(); } - // By completing PipeWriter, tell the PipeReader that there's no more data coming. - await writer.CompleteAsync(); - } - - /// - /// 从Pipe中读取收到的消息 - /// - /// - /// - /// - private async Task ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken) - { - while (true) + /// + /// 从Pipe中读取收到的消息 + /// + /// + /// + /// + private async Task ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken) { - var result = await reader.ReadAsync(cancellationToken); - var buffer = result.Buffer; - - while (TryReadLine(ref buffer, out ReadOnlySequence line)) + while (true) { - // Process the line. - ProcessLine(line, cancellationToken); + var result = await reader.ReadAsync(cancellationToken); + var buffer = result.Buffer; + + while (TryReadLine(ref buffer, out ReadOnlySequence line)) + { + // Process the line. + ProcessLine(line, cancellationToken); + } + + // Tell the PipeReader how much of the buffer has been consumed. + reader.AdvanceTo(buffer.Start, buffer.End); + + // Stop reading if there's no more data coming. + if (result.IsCompleted) + { + break; + } } - // Tell the PipeReader how much of the buffer has been consumed. - reader.AdvanceTo(buffer.Start, buffer.End); - - // Stop reading if there's no more data coming. - if (result.IsCompleted) - { - break; - } + // Mark the PipeReader as complete. + await reader.CompleteAsync(); } - // Mark the PipeReader as complete. - await reader.CompleteAsync(); - } - - bool TryReadLine(ref ReadOnlySequence buffer, out ReadOnlySequence line) - { - // Look for a EOL in the buffer. - SequencePosition? position = buffer.PositionOf((byte)'\n'); - - if (position == null) + bool TryReadLine(ref ReadOnlySequence buffer, out ReadOnlySequence line) { - line = default; - return false; - } + // Look for a EOL in the buffer. + SequencePosition? position = buffer.PositionOf((byte)'\n'); - // Skip the line + the \n. - line = buffer.Slice(0, position.Value); - buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); - return true; + if (position == null) + { + line = default; + return false; + } + + // Skip the line + the \n. + line = buffer.Slice(0, position.Value); + buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); + return true; + } } } diff --git a/FastTunnel.Server/FastTunnel.Server.csproj b/FastTunnel.Server/FastTunnel.Server.csproj index c7bd9ba..b5935ff 100644 --- a/FastTunnel.Server/FastTunnel.Server.csproj +++ b/FastTunnel.Server/FastTunnel.Server.csproj @@ -1,7 +1,7 @@ - net6.0 + net7.0 diff --git a/FastTunnel.Server/Program.cs b/FastTunnel.Server/Program.cs index 8a5c61a..ce41f81 100644 --- a/FastTunnel.Server/Program.cs +++ b/FastTunnel.Server/Program.cs @@ -19,8 +19,9 @@ public class Program CreateHostBuilder(args).Build().Run(); } - public static IHostBuilder CreateHostBuilder(string[] args) => - Host.CreateDefaultBuilder(args) + public static IHostBuilder CreateHostBuilder(string[] args) + { + return Host.CreateDefaultBuilder(args) .UseSerilog((context, services, configuration) => configuration .ReadFrom.Configuration(context.Configuration) .ReadFrom.Services(services) @@ -43,4 +44,5 @@ public class Program { webBuilder.UseStartup(); }); + } } diff --git a/FastTunnel.Server/Properties/launchSettings.json b/FastTunnel.Server/Properties/launchSettings.json index 33355cb..86dc5a8 100644 --- a/FastTunnel.Server/Properties/launchSettings.json +++ b/FastTunnel.Server/Properties/launchSettings.json @@ -13,24 +13,10 @@ "commandName": "Project", "launchBrowser": true, "launchUrl": "http://localhost:1270/swagger", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development", - //"ASPNETCORE_HOSTINGSTARTUPASSEMBLIES": "FastTunnel.Api;FastTunnel.Core" - }, - "applicationUrl": "https://localhost:5001;http://localhost:5000" - }, - "IIS Express": { - "commandName": "IISExpress", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "Docker": { - "commandName": "Docker", - "launchBrowser": true, - "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", - "publishAllPorts": true, - "useSSL": true + }, + "applicationUrl": "http://localhost:1270" } } } diff --git a/FastTunnel.sln b/FastTunnel.sln index 6a3916b..75e9a92 100644 --- a/FastTunnel.sln +++ b/FastTunnel.sln @@ -19,6 +19,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .github\workflows\dotnetcore.yml = .github\workflows\dotnetcore.yml EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastTunnel.Core.Client", "FastTunnel.Core.Client\FastTunnel.Core.Client.csproj", "{67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -41,6 +43,10 @@ Global {7D560A9A-E480-40F4-AAF7-398447438255}.Debug|Any CPU.Build.0 = Debug|Any CPU {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.ActiveCfg = Release|Any CPU {7D560A9A-E480-40F4-AAF7-398447438255}.Release|Any CPU.Build.0 = Release|Any CPU + {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -48,6 +54,7 @@ Global GlobalSection(NestedProjects) = preSolution {C8ADFEB1-59DB-4CE3-8D04-5B547107BCCB} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} {7D560A9A-E480-40F4-AAF7-398447438255} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} + {67DB3ED6-B1DD-49AA-8C5D-34FABC3C643B} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3D9C6B44-6706-4EE8-9043-802BBE474A2E}