This commit is contained in:
Gui.H 2022-11-18 16:34:02 +08:00
commit 5fae2ea387
37 changed files with 608 additions and 261 deletions

View File

@ -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]
#### ÃüÃûÑùʽ ####

View File

@ -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

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net7.0</TargetFrameworks>
<Version>1.1.0</Version>
<PackageProjectUrl>https://github.com/FastTunnel/FastTunnel/tree/v2/FastTunnel.Api</PackageProjectUrl>
<RepositoryUrl>https://github.com/FastTunnel/FastTunnel/tree/v2/FastTunnel.Api</RepositoryUrl>

View File

@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
@ -13,7 +13,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
<ProjectReference Include="..\FastTunnel.Core.Client\FastTunnel.Core.Client.csproj" />
</ItemGroup>
<ItemGroup>

View File

@ -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;

View File

@ -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<ForwardConfig> Forwards { get; set; }
}
}
}

View File

@ -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
{
/// <summary>
/// 客户端依赖及HostedService
/// </summary>
/// <param name="services"></param>
public static void AddFastTunnelClient(this IServiceCollection services, IConfigurationSection configurationSection)
{
services.Configure<DefaultClientConfig>(configurationSection);
services.AddTransient<IFastTunnelClient, FastTunnelClient>()
.AddSingleton<LogHandler>()
.AddSingleton<SwapHandler>();
services.AddHostedService<ServiceFastTunnelClient>();
}
}
}

View File

@ -0,0 +1,39 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>2.1.1</Version>
<TargetFrameworks>netcoreapp3.1;net5.0;net6.0;net7.0;</TargetFrameworks>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\FastTunnel.Core\Exceptions\SocketClosedException.cs" Link="Exceptions\SocketClosedException.cs" />
<Compile Include="..\FastTunnel.Core\Extensions\LoggerExtentions.cs" Link="Extensions\LoggerExtentions.cs" />
<Compile Include="..\FastTunnel.Core\Extensions\ObjectExtensions.cs" Link="Extensions\ObjectExtensions.cs" />
<Compile Include="..\FastTunnel.Core\Extensions\WebSocketExtensions.cs" Link="Extensions\WebSocketExtensions.cs" />
<Compile Include="..\FastTunnel.Core\FastTunnelConst.cs" Link="FastTunnelConst.cs" />
<Compile Include="..\FastTunnel.Core\Models\ForwardConfig.cs" Link="Models\ForwardConfig.cs" />
<Compile Include="..\FastTunnel.Core\Models\LogInMassage.cs" Link="Models\LogInMassage.cs" />
<Compile Include="..\FastTunnel.Core\Models\Message.cs" Link="Models\Message.cs" />
<Compile Include="..\FastTunnel.Core\Models\TunnelMassage.cs" Link="Models\TunnelMassage.cs" />
<Compile Include="..\FastTunnel.Core\Models\WebConfig.cs" Link="Models\WebConfig.cs" />
<Compile Include="..\FastTunnel.Core\Utilitys\WebSocketUtility.cs" Link="Utilitys\WebSocketUtility.cs" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="System.IO.Pipelines" Version="5.0.2" />
</ItemGroup>
<ItemGroup>
<Folder Include="Exceptions\" />
</ItemGroup>
</Project>

View File

@ -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)

View File

@ -26,4 +26,5 @@ namespace FastTunnel.Core.Handlers.Client
/// <returns></returns>
Task HandlerMsgAsync(FastTunnelClient cleint, string msg, CancellationToken cancellationToken);
}
}

View File

@ -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<SwapHandler> _logger;
static int connectionCount;
public SwapHandler(ILogger<SwapHandler> 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}");
}
}

View File

@ -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<ForwardConfig> Forwards { get; set; }
}
public class SuiDaoServer
{
public string Protocol { get; set; } = "ws";
public string ServerAddr { get; set; }
public int ServerPort { get; set; }
}
}

View File

@ -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; }
}
}

View File

@ -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;

View File

@ -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<Socket> 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;
}
}
}

View File

@ -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();
}

View File

@ -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
}
}

View File

@ -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
{
/// <summary>
/// 客户端依赖及HostedService
/// </summary>
/// <param name="services"></param>
public static void AddFastTunnelClient(this IServiceCollection services, IConfigurationSection configurationSection)
{
services.Configure<DefaultClientConfig>(configurationSection);
services.AddFastTunnelClient();
}
public static void AddFastTunnelClient(this IServiceCollection services)
{
services.AddTransient<IFastTunnelClient, FastTunnelClient>()
.AddSingleton<IExceptionFilter, FastTunnelExceptionFilter>()
.AddSingleton<LogHandler>()
.AddSingleton<SwapHandler>();
services.AddHostedService<ServiceFastTunnelClient>();
}
/// <summary>
/// 添加服务端后台进程

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<Version>2.1.1</Version>
<PackageProjectUrl>https://github.com/SpringHgui/FastTunnel</PackageProjectUrl>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
@ -17,6 +17,7 @@
<PackageTags>FastTunnel.Core</PackageTags>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<PackageReleaseNotes>FastTunnel.Core</PackageReleaseNotes>
<LangVersion>Preview</LangVersion>
</PropertyGroup>
<ItemGroup>

View File

@ -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<FastTunnelForwarderHttpClientFactory> logger;
readonly FastTunnelServer fastTunnelServer;
private readonly IHttpContextAccessor _httpContextAccessor;
static int connectionCount;
public FastTunnelForwarderHttpClientFactory(
ILogger<FastTunnelForwarderHttpClientFactory> logger,
IHttpContextAccessor httpContextAccessor, FastTunnelServer fastTunnelServer)
{
readonly ILogger<FastTunnelForwarderHttpClientFactory> logger;
readonly FastTunnelServer fastTunnelServer;
private readonly IHttpContextAccessor _httpContextAccessor;
this.fastTunnelServer = fastTunnelServer;
this.logger = logger;
_httpContextAccessor = httpContextAccessor;
}
public FastTunnelForwarderHttpClientFactory(
ILogger<FastTunnelForwarderHttpClientFactory> logger,
IHttpContextAccessor httpContextAccessor, FastTunnelServer fastTunnelServer)
protected override void ConfigureHandler(ForwarderHttpClientContext context, SocketsHttpHandler handler)
{
base.ConfigureHandler(context, handler);
handler.ConnectCallback = ConnectCallback;
}
private async ValueTask<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
{
var host = context.InitialRequestMessage.RequestUri.Host;
var contextRequest = _httpContextAccessor.HttpContext;
//var lifetime = contextRequest.Features.Get<IConnectionLifetimeFeature>()!;
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<Stream> ConnectCallback(SocketsHttpConnectionContext context, CancellationToken cancellationToken)
finally
{
var host = context.InitialRequestMessage.RequestUri.Host;
var contextRequest = _httpContextAccessor.HttpContext;
//var lifetime = contextRequest.Features.Get<IConnectionLifetimeFeature>()!;
try
{
var res = await proxyAsync(host, context, contextRequest.RequestAborted);
return res;
}
catch (Exception)
{
throw;
}
}
public async ValueTask<Stream> 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<Stream> 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<Stream> 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<Stream> 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<Stream> 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<Stream> 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));
}
}

View File

@ -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<FastTunnelClientHandler> 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)
{

View File

@ -16,6 +16,9 @@ namespace FastTunnel.Core.Forwarder.MiddleWare
{
ILogger<FastTunnelClientHandler> logger;
FastTunnelServer fastTunnelServer;
static int connectionCount;
public static int ConnectionCount => connectionCount;
public FastTunnelSwapHandler(ILogger<FastTunnelClientHandler> logger, FastTunnelServer fastTunnelServer)
{
@ -25,6 +28,8 @@ namespace FastTunnel.Core.Forwarder.MiddleWare
public async Task Handle(HttpContext context, Func<Task> 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}");
}
}
}
}

View File

@ -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;

View File

@ -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<TcpServerHandler>();
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
{
}
}
}
}

View File

@ -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;
}
}

View File

@ -22,7 +22,7 @@ namespace FastTunnel.Core.Models
/// 服务端监听的端口号 1~65535
/// </summary>
public int RemotePort { get; set; }
/// <summary>
/// 协议,内网服务监听的协议
/// </summary>

View File

@ -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<ReadOnlySequence<byte>, CancellationToken> processLine)
public class WebSocketUtility
{
this.webSocket = webSocket;
ProcessLine = processLine;
}
private readonly WebSocket webSocket;
public Action<ReadOnlySequence<byte>, 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);
}
/// <summary>
/// 读取socket收到的消息写入Pipe
/// </summary>
/// <param name="socket"></param>
/// <param name="writer"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
private async Task FillPipeAsync(WebSocket socket, PipeWriter writer, CancellationToken cancellationToken)
{
const int minimumBufferSize = 512;
while (true)
public WebSocketUtility(WebSocket webSocket, Action<ReadOnlySequence<byte>, CancellationToken> processLine)
{
// Allocate at least 512 bytes from the PipeWriter.
var memory = writer.GetMemory(minimumBufferSize);
this.webSocket = webSocket;
ProcessLine = processLine;
}
try
public Action<ReadOnlySequence<byte>, 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);
}
/// <summary>
/// 读取socket收到的消息写入Pipe
/// </summary>
/// <param name="socket"></param>
/// <param name="writer"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
/// <exception cref="NotImplementedException"></exception>
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();
}
/// <summary>
/// 从Pipe中读取收到的消息
/// </summary>
/// <param name="reader"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken)
{
while (true)
/// <summary>
/// 从Pipe中读取收到的消息
/// </summary>
/// <param name="reader"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
private async Task ReadPipeAsync(PipeReader reader, CancellationToken cancellationToken)
{
var result = await reader.ReadAsync(cancellationToken);
var buffer = result.Buffer;
while (TryReadLine(ref buffer, out ReadOnlySequence<byte> 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<byte> 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<byte> buffer, out ReadOnlySequence<byte> line)
{
// Look for a EOL in the buffer.
SequencePosition? position = buffer.PositionOf((byte)'\n');
if (position == null)
bool TryReadLine(ref ReadOnlySequence<byte> buffer, out ReadOnlySequence<byte> 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;
}
}
}

View File

@ -1,7 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
<TargetFramework>net7.0</TargetFramework>
</PropertyGroup>
<ItemGroup>

View File

@ -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<Startup>();
});
}
}

View File

@ -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"
}
}
}

View File

@ -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}