# Conflicts:
#	FastTunnel.Client/FastTunnel.Client.csproj
#	FastTunnel.Core/Forwarder/Kestrel/MiddleWare/ForwarderMiddleware.cs
#	FastTunnel.Core/Forwarder/Kestrel/MiddleWare/InitializerMiddleware.cs
This commit is contained in:
Gui.H 2022-07-01 18:00:33 +08:00
commit 224fee149b
30 changed files with 607 additions and 543 deletions

View File

@ -1,7 +1,5 @@
<Project>
<PropertyGroup>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFrameworks>net6.0</TargetFrameworks>
<PackageProjectUrl>https://github.com/SpringHgui/FastTunnel</PackageProjectUrl>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Copyright>FastTunnel</Copyright>

View File

@ -1,12 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.4" />
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="6.17.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.17.0" />
</ItemGroup>

View File

@ -0,0 +1,80 @@
// 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.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FastTunnel.Api;
using FastTunnel.Api.Filters;
using FastTunnel.Core.Config;
using FastTunnel.Core.Extensions;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
[assembly: HostingStartup(typeof(FastTunnelApiHostingStartup))]
namespace FastTunnel.Api;
public class FastTunnelApiHostingStartup : IHostingStartup
{
public void Configure(IWebHostBuilder builder)
{
Debug.WriteLine("FastTunnelApiHostingStartup Configured");
builder.ConfigureServices((webHostBuilderContext, services) =>
{
services.AddControllers();
services.AddAuthorization();
var serverOptions = webHostBuilderContext.Configuration.GetSection("FastTunnel").Get<DefaultServerConfig>();
if (serverOptions.Api?.JWT != null)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(serverOptions.Api.JWT.ClockSkew),
ValidateIssuerSigningKey = true,
ValidAudience = serverOptions.Api.JWT.ValidAudience,
ValidIssuer = serverOptions.Api.JWT.ValidIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(serverOptions.Api.JWT.IssuerSigningKey))
};
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
context.HandleResponse();
context.Response.ContentType = "application/json;charset=utf-8";
context.Response.StatusCode = StatusCodes.Status200OK;
await context.Response.WriteAsync(new
{
errorCode = 1,
errorMessage = context.Error ?? "Token is Required"
}.ToJson());
},
};
});
}
services.AddSingleton<CustomExceptionFilterAttribute>();
});
}
}

View File

@ -1,33 +1,35 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
</PropertyGroup>
<PropertyGroup>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFramework>net7.0</TargetFramework>
<OutputType>Exe</OutputType>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting.WindowsServices" Version="7.0.0-preview.3.22175.4" />
<PackageReference Include="Serilog.AspNetCore" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="install.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="uninstall.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="install.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="uninstall.bat">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
<ProjectExtensions>
<VisualStudio>
<UserProperties appsettings_1json__JsonSchema="" />
</VisualStudio>
</ProjectExtensions>
<ProjectExtensions>
<VisualStudio>
<UserProperties appsettings_1json__JsonSchema="" />
</VisualStudio>
</ProjectExtensions>
</Project>

View File

@ -27,7 +27,7 @@
// [] ip
"LocalIp": "127.0.0.1",
// []
"LocalPort": 8080,
"LocalPort": 8090,
// [] , 访url http://${SubDomain}.${WebDomain}:${ServerPort}
"SubDomain": "test"

View File

@ -23,13 +23,12 @@ public static class ListenOptionsSwapExtensions
{
var fastTunnelServer = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<FastTunnelServer>();
var loggerFactory = listenOptions.KestrelServerOptions.ApplicationServices.GetRequiredService<ILoggerFactory>();
var logger = loggerFactory.CreateLogger<SwapConnectionMiddleware>();
var loggerHttp = loggerFactory.CreateLogger<FastTunnelConnectionMiddleware>();
var forwardLogger = loggerFactory.CreateLogger<ForwarderMiddleware>();
var initLogger = loggerFactory.CreateLogger<InitializerMiddleware>();
listenOptions.Use(next => new FastTunnelConnectionMiddleware(next, loggerHttp, fastTunnelServer).OnConnectionAsync);
listenOptions.Use(next => new SwapConnectionMiddleware(next, logger, fastTunnelServer).OnConnectionAsync);
listenOptions.Use(next => new InitializerMiddleware(next, initLogger, fastTunnelServer).OnConnectionAsync);
listenOptions.Use(next => new ForwarderMiddleware(next, forwardLogger, fastTunnelServer).OnConnectionAsync);
// 登录频率低,放在后面
return listenOptions;
}
}

View File

@ -1,9 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFrameworks>net6.0</TargetFrameworks>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup>
<Compile Remove="Forwarder\Kestrel\FastTunnelConnectionContext.cs" />
</ItemGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>

View File

@ -8,59 +8,43 @@ using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;
using FastTunnel.Core.Models;
using FastTunnel.Core.Protocol;
using FastTunnel.Core.Server;
using Microsoft.AspNetCore.Connections;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.FileSystemGlobbing;
namespace FastTunnel.Core.Forwarder.Kestrel;
internal class FastTunnelConnectionContext : ConnectionContext
namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
public class FastTunelProtocol
{
private readonly ConnectionContext _inner;
private readonly FastTunnelServer fastTunnelServer;
private readonly ILogger _logger;
public FastTunnelConnectionContext(ConnectionContext context, FastTunnelServer fastTunnelServer, ILogger logger)
public FastTunelProtocol(ConnectionContext context, FastTunnelServer fastTunnelServer)
{
this.context = context;
this.fastTunnelServer = fastTunnelServer;
this._inner = context;
this._logger = logger;
}
public override IDuplexPipe Transport { get => _inner.Transport; set => _inner.Transport = value; }
public override string ConnectionId { get => _inner.ConnectionId; set => _inner.ConnectionId = value; }
public override IFeatureCollection Features => _inner.Features;
public override IDictionary<object, object> Items { get => _inner.Items; set => _inner.Items = value; }
public bool IsFastTunnel => Method == ProtocolConst.HTTP_METHOD_SWAP || MatchWeb != null;
bool IsFastTunnel => Method == ProtocolConst.HTTP_METHOD_SWAP || MatchWeb != null;
public WebInfo MatchWeb { get; private set; }
public override ValueTask DisposeAsync()
{
return _inner.DisposeAsync();
}
ConnectionContext context { get; }
IDuplexPipe Transport => context.Transport;
/// <summary>
/// 解析FastTunnel协议
/// </summary>
internal async Task TryAnalysisPipeAsync()
{
var reader = Transport.Input;
var _input = Transport.Input;
ReadResult result;
ReadOnlySequence<byte> readableBuffer;
while (true)
{
result = await reader.ReadAsync();
result = await _input.ReadAsync(context.ConnectionClosed);
var tempBuffer = readableBuffer = result.Buffer;
SequencePosition? position = null;
@ -72,17 +56,28 @@ internal class FastTunnelConnectionContext : ConnectionContext
if (position != null)
{
var readedPosition = readableBuffer.GetPosition(1, position.Value);
if (ProcessLine(tempBuffer.Slice(0, position.Value)))
if (ProcessLine(tempBuffer.Slice(0, position.Value), out string line))
{
if (Method == ProtocolConst.HTTP_METHOD_SWAP)
{
reader.AdvanceTo(readedPosition, readedPosition);
_input.AdvanceTo(readedPosition, readedPosition);
}
else
{
reader.AdvanceTo(readableBuffer.Start, readableBuffer.Start);
_input.AdvanceTo(readableBuffer.Start, readableBuffer.Start);
}
if (IsFastTunnel)
{
context.Features.Set<IFastTunnelFeature>(new FastTunnelFeature()
{
MatchWeb = MatchWeb,
HasReadLInes = HasReadLInes,
Method = Method,
Host = Host,
MessageId = MessageId,
});
}
return;
}
@ -93,12 +88,10 @@ internal class FastTunnelConnectionContext : ConnectionContext
if (result.IsCompleted)
{
reader.AdvanceTo(readableBuffer.End, readableBuffer.End);
break;
_input.AdvanceTo(readableBuffer.End, readableBuffer.End);
return;
}
}
return;
}
public string Method;
@ -107,6 +100,7 @@ internal class FastTunnelConnectionContext : ConnectionContext
private bool isFirstLine = true;
public IList<string> HasReadLInes { get; private set; } = new List<string>();
public FastTunnelServer fastTunnelServer { get; }
/// <summary>
///
@ -122,9 +116,9 @@ internal class FastTunnelConnectionContext : ConnectionContext
/// </summary>
/// <param name="readOnlySequence"></param>
/// <returns>Header读取完毕</returns>
private bool ProcessLine(ReadOnlySequence<byte> readOnlySequence)
private bool ProcessLine(ReadOnlySequence<byte> readOnlySequence, out string lineStr)
{
var lineStr = Encoding.UTF8.GetString(readOnlySequence);
lineStr = Encoding.UTF8.GetString(readOnlySequence);
HasReadLInes.Add(lineStr);
if (isFirstLine)

View File

@ -0,0 +1,24 @@
// 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 FastTunnel.Core.Models;
namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
public class FastTunnelFeature : IFastTunnelFeature
{
public WebInfo MatchWeb { get; set; }
public IList<string> HasReadLInes { get; set; }
public string Method { get; set; }
public string Host { get; set; }
public string MessageId { get; set; }
}

View File

@ -28,13 +28,13 @@ namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
/// <summary>
/// 核心逻辑处理中间件
/// </summary>
internal class SwapConnectionMiddleware
internal class ForwarderMiddleware
{
private readonly ConnectionDelegate next;
private readonly ILogger<SwapConnectionMiddleware> logger;
private readonly ILogger<ForwarderMiddleware> logger;
private readonly FastTunnelServer fastTunnelServer;
public SwapConnectionMiddleware(ConnectionDelegate next, ILogger<SwapConnectionMiddleware> logger, FastTunnelServer fastTunnelServer)
public ForwarderMiddleware(ConnectionDelegate next, ILogger<ForwarderMiddleware> logger, FastTunnelServer fastTunnelServer)
{
this.next = next;
this.logger = logger;
@ -43,35 +43,45 @@ internal class SwapConnectionMiddleware
internal async Task OnConnectionAsync(ConnectionContext context)
{
var ctx = context as FastTunnelConnectionContext;
if (ctx != null && ctx.IsFastTunnel)
logger.LogInformation("=========ForwarderMiddleware SART===========");
var feat = context.Features.Get<IFastTunnelFeature>();
if (feat == null)
{
if (ctx.Method == ProtocolConst.HTTP_METHOD_SWAP)
logger.LogInformation("=========ForwarderMiddleware END===========");
// not fasttunnel request
await next(context);
return;
}
else
{
logger.LogInformation("=========Swap STRART===========");
if (feat.Method == ProtocolConst.HTTP_METHOD_SWAP)
{
await setResponse(ctx);
await doSwap(context);
}
else if (ctx.MatchWeb != null)
else if (feat.MatchWeb != null)
{
await waitResponse(ctx);
await waitSwap(context);
}
else
{
throw new NotSupportedException();
}
}
else
{
await next(context);
logger.LogInformation("=========Swap END===========");
logger.LogInformation("=========ForwarderMiddleware END===========");
}
}
private async Task waitResponse(FastTunnelConnectionContext context)
private async Task waitSwap(ConnectionContext context)
{
var feat = context.Features.Get<IFastTunnelFeature>();
var requestId = Guid.NewGuid().ToString().Replace("-", "");
var web = context.MatchWeb;
var web = feat.MatchWeb;
TaskCompletionSource<IDuplexPipe> tcs = new();
logger.LogDebug($"[Http]Swap开始 {requestId}|{context.Host}=>{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}");
logger.LogDebug($"[Http]Swap开始 {requestId}|{feat.Host}=>{web.WebConfig.LocalIp}:{web.WebConfig.LocalPort}");
tcs.SetTimeOut(10000, () => { logger.LogDebug($"[Proxy TimeOut]:{requestId}"); });
fastTunnelServer.ResponseTasks.TryAdd(requestId, tcs);
@ -99,8 +109,8 @@ internal class SwapConnectionMiddleware
// using var reverseConnection = new DuplexPipeStream(context.Transport.Input, context.Transport.Output, true);
var t1 = res.Input.CopyToAsync(context.Transport.Output, lifetime.ConnectionClosed);
var t2 = context.Transport.Input.CopyToAsync(res.Output, lifetime.ConnectionClosed);
var t1 = res.Input.CopyToAsync(context.Transport.Output, context.ConnectionClosed);
var t2 = context.Transport.Input.CopyToAsync(res.Output, context.ConnectionClosed);
await Task.WhenAny(t1, t2);
}
catch (Exception ex)
@ -120,9 +130,10 @@ internal class SwapConnectionMiddleware
}
}
private async Task setResponse(FastTunnelConnectionContext context)
private async Task doSwap(ConnectionContext context)
{
var requestId = context.MessageId;
var feat = context.Features.Get<IFastTunnelFeature>();
var requestId = feat.MessageId;
if (!fastTunnelServer.ResponseTasks.TryRemove(requestId, out var responseStream))
{
throw new Exception($"[PROXY]:RequestId不存在 {requestId}");

View File

@ -0,0 +1,26 @@
// 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 FastTunnel.Core.Models;
using FastTunnel.Core.Protocol;
namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
internal interface IFastTunnelFeature
{
public WebInfo MatchWeb { get; set; }
public IList<string> HasReadLInes { get; set; }
public string Method { get; set; }
public string Host { get; set; }
public string MessageId { get; set; }
}

View File

@ -15,13 +15,13 @@ namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
/// <summary>
/// 预处理中间件
/// </summary>
internal class FastTunnelConnectionMiddleware
internal class InitializerMiddleware
{
private readonly ConnectionDelegate next;
private readonly ILogger<FastTunnelConnectionMiddleware> logger;
private readonly ILogger<InitializerMiddleware> logger;
private readonly FastTunnelServer fastTunnelServer;
public FastTunnelConnectionMiddleware(ConnectionDelegate next, ILogger<FastTunnelConnectionMiddleware> logger, FastTunnelServer fastTunnelServer)
public InitializerMiddleware(ConnectionDelegate next, ILogger<InitializerMiddleware> logger, FastTunnelServer fastTunnelServer)
{
this.next = next;
this.logger = logger;
@ -30,18 +30,10 @@ internal class FastTunnelConnectionMiddleware
internal async Task OnConnectionAsync(ConnectionContext context)
{
logger.LogInformation("=========OnConnectionAsync===========");
var ftContext = new FastTunnelConnectionContext(context, fastTunnelServer, logger);
await ftContext.TryAnalysisPipeAsync();
logger.LogInformation("=========TryAnalysisPipeAsync SART===========");
await new FastTunelProtocol(context, fastTunnelServer).TryAnalysisPipeAsync();
logger.LogInformation("=========TryAnalysisPipeAsync END===========");
if (ftContext.IsFastTunnel)
{
await next(ftContext.IsFastTunnel ? ftContext : context);
}
else
{
await next(context);
}
await next(context);
}
}

View File

@ -1,112 +0,0 @@
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastTunnel.Core.Forwarder.Kestrel.MiddleWare
{
internal static class TaskToApm
{
/// <summary>
/// Marshals the Task as an IAsyncResult, using the supplied callback and state
/// to implement the APM pattern.
/// </summary>
/// <param name="task">The Task to be marshaled.</param>
/// <param name="callback">The callback to be invoked upon completion.</param>
/// <param name="state">The state to be stored in the IAsyncResult.</param>
/// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) =>
new TaskAsyncResult(task, state, callback);
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static void End(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar)
{
twar._task.GetAwaiter().GetResult();
return;
}
throw new ArgumentNullException(nameof(asyncResult));
}
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static TResult End<TResult>(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar && twar._task is Task<TResult> task)
{
return task.GetAwaiter().GetResult();
}
throw new ArgumentNullException(nameof(asyncResult));
}
/// <summary>Provides a simple IAsyncResult that wraps a Task.</summary>
/// <remarks>
/// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
/// but that's very rare, in particular in a situation where someone cares about allocation, and always
/// using TaskAsyncResult simplifies things and enables additional optimizations.
/// </remarks>
internal sealed class TaskAsyncResult : IAsyncResult
{
/// <summary>The wrapped Task.</summary>
internal readonly Task _task;
/// <summary>Callback to invoke when the wrapped task completes.</summary>
private readonly AsyncCallback _callback;
/// <summary>Initializes the IAsyncResult with the Task to wrap and the associated object state.</summary>
/// <param name="task">The Task to wrap.</param>
/// <param name="state">The new AsyncState value.</param>
/// <param name="callback">Callback to invoke when the wrapped task completes.</param>
internal TaskAsyncResult(Task task, object state, AsyncCallback callback)
{
Debug.Assert(task != null);
_task = task;
AsyncState = state;
if (task.IsCompleted)
{
// Synchronous completion. Invoke the callback. No need to store it.
CompletedSynchronously = true;
callback?.Invoke(this);
}
else if (callback != null)
{
// Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
// order to avoid running synchronously if the task has already completed by the time we get here but still run
// synchronously as part of the task's completion if the task completes after (the more common case).
_callback = callback;
_task.ConfigureAwait(continueOnCapturedContext: false)
.GetAwaiter()
.OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure
}
}
/// <summary>Invokes the callback.</summary>
private void InvokeCallback()
{
Debug.Assert(!CompletedSynchronously);
Debug.Assert(_callback != null);
_callback.Invoke(this);
}
/// <summary>Gets a user-defined object that qualifies or contains information about an asynchronous operation.</summary>
public object AsyncState { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation completed synchronously.</summary>
/// <remarks>This is set lazily based on whether the <see cref="_task"/> has completed by the time this object is created.</remarks>
public bool CompletedSynchronously { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation has completed.</summary>
public bool IsCompleted => _task.IsCompleted;
/// <summary>Gets a <see cref="WaitHandle"/> that is used to wait for an asynchronous operation to complete.</summary>
public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle;
}
}
}

View File

@ -13,7 +13,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Forwarder.Kestrel.MiddleWare;
using FastTunnel.Core.Refs;
namespace FastTunnel.Core.Forwarder.MiddleWare;

View File

@ -0,0 +1,173 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Buffers;
using System.IO;
using System.IO.Pipelines;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using FastTunnel.Core.Extensions;
using Microsoft.AspNetCore.Internal;
#nullable enable
namespace FastTunnel.Core.Refs;
internal class DuplexPipeStream : Stream
{
private readonly PipeReader _input;
private readonly PipeWriter _output;
private readonly bool _throwOnCancelled;
private volatile bool _cancelCalled;
public DuplexPipeStream(PipeReader input, PipeWriter output, bool throwOnCancelled = false)
{
_input = input;
_output = output;
_throwOnCancelled = throwOnCancelled;
}
public void CancelPendingRead()
{
_cancelCalled = true;
_input.CancelPendingRead();
}
public override bool CanRead => true;
public override bool CanSeek => false;
public override bool CanWrite => true;
public override long Length
{
get
{
throw new NotSupportedException();
}
}
public override long Position
{
get
{
throw new NotSupportedException();
}
set
{
throw new NotSupportedException();
}
}
public override long Seek(long offset, SeekOrigin origin)
{
throw new NotSupportedException();
}
public override void SetLength(long value)
{
throw new NotSupportedException();
}
public override int Read(byte[] buffer, int offset, int count)
{
var vt = ReadAsyncInternal(new Memory<byte>(buffer, offset, count), default);
return vt.IsCompleted ?
vt.Result :
vt.AsTask().GetAwaiter().GetResult();
}
public override Task<int> ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(new Memory<byte>(buffer, offset, count), cancellationToken).AsTask();
}
public override ValueTask<int> ReadAsync(Memory<byte> destination, CancellationToken cancellationToken = default)
{
return ReadAsyncInternal(destination, cancellationToken);
}
public override void Write(byte[] buffer, int offset, int count)
{
WriteAsync(buffer, offset, count).GetAwaiter().GetResult();
}
public override Task WriteAsync(byte[]? buffer, int offset, int count, CancellationToken cancellationToken)
{
return _output.WriteAsync(buffer.AsMemory(offset, count), cancellationToken).GetAsTask();
}
public override ValueTask WriteAsync(ReadOnlyMemory<byte> source, CancellationToken cancellationToken = default)
{
return _output.WriteAsync(source, cancellationToken).GetAsValueTask();
}
public override void Flush()
{
FlushAsync(CancellationToken.None).GetAwaiter().GetResult();
}
public override Task FlushAsync(CancellationToken cancellationToken)
{
return _output.FlushAsync(cancellationToken).GetAsTask();
}
[AsyncMethodBuilder(typeof(PoolingAsyncValueTaskMethodBuilder<>))]
private async ValueTask<int> ReadAsyncInternal(Memory<byte> destination, CancellationToken cancellationToken)
{
while (true)
{
var result = await _input.ReadAsync(cancellationToken);
var readableBuffer = result.Buffer;
try
{
if (_throwOnCancelled && result.IsCanceled && _cancelCalled)
{
// Reset the bool
_cancelCalled = false;
throw new OperationCanceledException();
}
if (!readableBuffer.IsEmpty)
{
// buffer.Count is int
var count = (int)Math.Min(readableBuffer.Length, destination.Length);
readableBuffer = readableBuffer.Slice(0, count);
readableBuffer.CopyTo(destination.Span);
return count;
}
if (result.IsCompleted)
{
return 0;
}
}
finally
{
_input.AdvanceTo(readableBuffer.End, readableBuffer.End);
}
}
}
public override IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(ReadAsync(buffer, offset, count), callback, state);
}
public override int EndRead(IAsyncResult asyncResult)
{
return TaskToApm.End<int>(asyncResult);
}
public override IAsyncResult BeginWrite(byte[] buffer, int offset, int count, AsyncCallback? callback, object? state)
{
return TaskToApm.Begin(WriteAsync(buffer, offset, count), callback, state);
}
public override void EndWrite(IAsyncResult asyncResult)
{
TaskToApm.End(asyncResult);
}
}

View File

@ -0,0 +1,57 @@
using System;
using System.IO;
using System.IO.Pipelines;
using System.Threading.Tasks;
namespace FastTunnel.Core.Refs;
/// <summary>
/// A helper for wrapping a Stream decorator from an <see cref="IDuplexPipe"/>.
/// </summary>
/// <typeparam name="TStream"></typeparam>
internal class DuplexPipeStreamAdapter<TStream> : DuplexPipeStream, IDuplexPipe where TStream : Stream
{
private bool _disposed;
private readonly object _disposeLock = new object();
public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, Func<Stream, TStream> createStream) :
this(duplexPipe, new StreamPipeReaderOptions(leaveOpen: true), new StreamPipeWriterOptions(leaveOpen: true), createStream)
{
}
public DuplexPipeStreamAdapter(IDuplexPipe duplexPipe, StreamPipeReaderOptions readerOptions, StreamPipeWriterOptions writerOptions, Func<Stream, TStream> createStream) :
base(duplexPipe.Input, duplexPipe.Output)
{
var stream = createStream(this);
Stream = stream;
Input = PipeReader.Create(stream, readerOptions);
Output = PipeWriter.Create(stream, writerOptions);
}
public TStream Stream { get; }
public PipeReader Input { get; }
public PipeWriter Output { get; }
public override async ValueTask DisposeAsync()
{
lock (_disposeLock)
{
if (_disposed)
{
return;
}
_disposed = true;
}
await Input.CompleteAsync();
await Output.CompleteAsync();
}
protected override void Dispose(bool disposing)
{
throw new NotSupportedException();
}
}

View File

@ -0,0 +1,111 @@
// 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.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace FastTunnel.Core.Refs;
internal static class TaskToApm
{
/// <summary>
/// Marshals the Task as an IAsyncResult, using the supplied callback and state
/// to implement the APM pattern.
/// </summary>
/// <param name="task">The Task to be marshaled.</param>
/// <param name="callback">The callback to be invoked upon completion.</param>
/// <param name="state">The state to be stored in the IAsyncResult.</param>
/// <returns>An IAsyncResult to represent the task's asynchronous operation.</returns>
public static IAsyncResult Begin(Task task, AsyncCallback callback, object state) =>
new TaskAsyncResult(task, state, callback);
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static void End(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar)
{
twar._task.GetAwaiter().GetResult();
return;
}
throw new ArgumentNullException(nameof(asyncResult));
}
/// <summary>Processes an IAsyncResult returned by Begin.</summary>
/// <param name="asyncResult">The IAsyncResult to unwrap.</param>
public static TResult End<TResult>(IAsyncResult asyncResult)
{
if (asyncResult is TaskAsyncResult twar && twar._task is Task<TResult> task)
{
return task.GetAwaiter().GetResult();
}
throw new ArgumentNullException(nameof(asyncResult));
}
/// <summary>Provides a simple IAsyncResult that wraps a Task.</summary>
/// <remarks>
/// We could use the Task as the IAsyncResult if the Task's AsyncState is the same as the object state,
/// but that's very rare, in particular in a situation where someone cares about allocation, and always
/// using TaskAsyncResult simplifies things and enables additional optimizations.
/// </remarks>
internal sealed class TaskAsyncResult : IAsyncResult
{
/// <summary>The wrapped Task.</summary>
internal readonly Task _task;
/// <summary>Callback to invoke when the wrapped task completes.</summary>
private readonly AsyncCallback _callback;
/// <summary>Initializes the IAsyncResult with the Task to wrap and the associated object state.</summary>
/// <param name="task">The Task to wrap.</param>
/// <param name="state">The new AsyncState value.</param>
/// <param name="callback">Callback to invoke when the wrapped task completes.</param>
internal TaskAsyncResult(Task task, object state, AsyncCallback callback)
{
Debug.Assert(task != null);
_task = task;
AsyncState = state;
if (task.IsCompleted)
{
// Synchronous completion. Invoke the callback. No need to store it.
CompletedSynchronously = true;
callback?.Invoke(this);
}
else if (callback != null)
{
// Asynchronous completion, and we have a callback; schedule it. We use OnCompleted rather than ContinueWith in
// order to avoid running synchronously if the task has already completed by the time we get here but still run
// synchronously as part of the task's completion if the task completes after (the more common case).
_callback = callback;
_task.ConfigureAwait(continueOnCapturedContext: false)
.GetAwaiter()
.OnCompleted(InvokeCallback); // allocates a delegate, but avoids a closure
}
}
/// <summary>Invokes the callback.</summary>
private void InvokeCallback()
{
Debug.Assert(!CompletedSynchronously);
Debug.Assert(_callback != null);
_callback.Invoke(this);
}
/// <summary>Gets a user-defined object that qualifies or contains information about an asynchronous operation.</summary>
public object AsyncState { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation completed synchronously.</summary>
/// <remarks>This is set lazily based on whether the <see cref="_task"/> has completed by the time this object is created.</remarks>
public bool CompletedSynchronously { get; }
/// <summary>Gets a value that indicates whether the asynchronous operation has completed.</summary>
public bool IsCompleted => _task.IsCompleted;
/// <summary>Gets a <see cref="WaitHandle"/> that is used to wait for an asynchronous operation to complete.</summary>
public WaitHandle AsyncWaitHandle => ((IAsyncResult)_task).AsyncWaitHandle;
}
}

View File

@ -1,10 +1,10 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFrameworks>net6.0;net7.0</TargetFrameworks>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<PackageReadmeFile>README.md</PackageReadmeFile>
<TargetFrameworks>net5.0;net6.0</TargetFrameworks>
</PropertyGroup>
<ItemGroup Condition="'$(TargetFramework)' == 'net5.0'">
@ -15,16 +15,15 @@
<PackageReference Include="Microsoft.AspNetCore.Hosting.Abstractions" Version="2.2.0" />
<PackageReference Include="Microsoft.Extensions.Configuration.Binder" Version="7.0.0-preview.3.22175.4" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
</ItemGroup>
<ItemGroup>
<None Include="README.md">
<None Include="..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
</ItemGroup>
</Project>

View File

@ -31,9 +31,9 @@ public class FastTunnelHostingStartup : IHostingStartup
});
});
builder.Configure((webHostBuilderContext, app) =>
{
app.UseFastTunnelServer();
});
//builder.Configure((webHostBuilderContext, app) =>
//{
// app.UseFastTunnelServer();
//});
}
}

View File

@ -1,166 +0,0 @@
<div align="center">
<img src="images/logo.png" width="150" align=center />
## FastTunnel
[![License](https://img.shields.io/badge/license-Apache%202-green.svg)](https://www.apache.org/licenses/LICENSE-2.0)
[![Build status](https://github.com/anjoy8/blog.core/workflows/.NET%20Core/badge.svg)](https://github.com/SpringHgui/FastTunnel/actions)
[![Nuget](https://img.shields.io/nuget/v/FastTunnel.Core)](https://www.nuget.org/packages/FastTunnel.Core/)
[![Nuget](https://img.shields.io/nuget/dt/FastTunnel.Core)](https://www.nuget.org/packages/FastTunnel.Core/)
[README](README.md) | [中文文档](README_zh.md)
***This project supports any commercial and secondary development activities, but seriously despises plagiarizing and copying the code, implementation scheme or architecture of this project and repackaging them into their own open source works.***
</div>
## What is FastTunnel
- FastTunnel is a high-performance cross-platform intranet penetration tool. With it, you can expose intranet services to the public network for yourself or anyone to access.
- Unlike other penetration tools, the FastTunnel project is committed to creating an easy-to-extensible and easy-to-maintain intranet penetration framework.
- You can build your own penetration application by referencing the nuget package of `FastTunnel.Core`, and target the business extension functions you need.
***
Official website : https://suidao.io
The penetration platform developed based on this framework, if you need intranet penetration, you can register and use it directly, eliminating the cost of building and maintaining yourself.
But do not use this service for important items.
OpenSource
GitHub : [FastTunnel](https://github.com/SpringHgui/FastTunnel)
Gitee: [FastTunnel](https://gitee.com/Hgui/FastTunnel)
**If helpful, click on ⭐Star to support this project, please submit an issue if you have needs and bugs, and welcome coder to PR**
## Get GVP
![img1](images/gvp.png)
***
## What can FastTunel do
- [x] Remote intranet computer Windows/Linux/Mac
- [x] Use a custom domain name to access intranet web services (usually used for WeChat development)
- [x] Port forwarding/port mapping, access services provided by any port on the intranet mysql, redis, ftp, etc.
- [ ] p2p penetration
- [x] Support binding multiple domain names to access intranet services
- [x] Support domain name whitelist restriction
- [x] Support client identity verification
## Quickstart
1. Download the corresponding program on the [releases](https://github.com/SpringHgui/FastTunnel/releases) page
2. Modify the client and server configuration files according to your needs`appsettings.json`
3. Run FastTunnel.Server
4. Run FastTunnel.Cient
## Install FastTunel.Sever using Docker Engine
Configuration files and log files are mounted through volume. If this image has been run before, docker may not update to the latest image. Please delete the existing image manually, and then execute the following command
```
docker run --detach \
--publish 1270:1270 --publish 1271:1271 \
--name FastTunnel \
--restart always \
--volume /var/FastTunnel/config:/app/config \
--volume /var/FastTunnel/Logs:/app/Logs \
springhgui/fasttunnel:latest
```
## Run on Linux/Mac os
#### Windows
Double click directly `FastTunnel.Client.exe` to run
#### Linux
`chmod +x FastTunnel.Client`
`./FastTunnel.Client`
#### Mac
click directly `FastTunnel.Client` to run
## Configuration example
### 1. Use a custom domain name to access intranet web services
- For example, you have a server with a public IP address of `110.110.110.110`, and you have a domain name with a top-level domain name of `abc.com`, you want to visit a website on the intranet by visiting `test.abc.com`
- You need to add a DNS resolution for the domain name address, the type is `A`, the name is `*`, and the ipv4 address is `110.110.110.110`, so that all domain names of `*.abc.com` will point to `110.110.110.110`s server, because the default http port of `FastTunnel` is 1270, so you need to visit`http://test.abc.com:1270`
- #### If you don't want to bring the port number every time you visit, you can use `nginx` forwarding.
```
http {
# add resolver
resolver 8.8.8.8;
# set *.abc.com to 1270 port
server {
server_name *.abc.com;
location / {
proxy_pass http://$host:1270;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# 可选
error_log /var/log/nginx/error_ft.log error;
}
}
```
- If the domain name configured on the server is `ft.suidao.io`, then access the local site through the subdomain name `test.ft.suidao.io:1270`, the IIS configuration is as follows:
![img1](images/iis-web.png)
### 2. Remote intranet computer Windows/Linux/Mac
The client configuration is as follows, there are two hosts in the intranet, and the ip is as follows:
appsettings.json
```
"ClientSettings": {
"Common": {
"ServerAddr": "xxx.xxx.xxx.xxx",
"ServerPort": 1271
},
"SSH": [
{
"LocalIp": "192.168.0.100", // linux pc
"LocalPort": 22, // ssh default port
"RemotePort": 12701
},
{
"LocalIp": "192.168.0.101", // windows pc
"LocalPort": 3389, // windows default port for Remote
"RemotePort": 12702
}
]
}
```
#### remote intranet linux host by ssh (ip:192.168.0.100)
Assuming that the user name of the intranet host is root, the server ip is x.x.x.x, and the two hosts that access the intranet are as follows
```
ssh -oPort=12701 root@x.x.x.x
```
#### remote desktop Windows host by mstsc (ip:192.168.0.101)
#### Controlled terminal setting
- Open cmd and enter the command `sysdm.cpl` in the pop-up dialog box and select Allow remote connection to this computer
![img1](images/setallow.png)
#### Control terminal settings
- Open cmd and enter the command `mstsc`, open the remote dialog box, enter `x.x.x.x:12701` in the computer input box of the dialog box, and then specify the user name and password to remote the windows host of the intranet
![img1](images/remote.png)
## Development/PR
- install `vs2019` last version
- install `.net5` or higher `https://dotnet.microsoft.com/download/dotnet/5.0`
- add `test.test.cc 127.0.0.1` in system host file
- run fasttunnel.server
- run fasttunnel.client
## contributors
<a href = "https://github.com/FastTunnel/FastTunnel/graphs/contributors">
<img src = "https://contrib.rocks/image?repo=FastTunnel/FastTunnel"/>
</a>
## Join QQ Group
<div align="center"><img src="images/qqgroup.png" width="150" align=center /></div>
## License
Apache License 2.0

View File

@ -1,6 +1,7 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks>net6.0</TargetFrameworks>
<Version>3.0.0-Beta.1.22511</Version>
<TargetFramework>net7.0</TargetFramework>
<RunAnalyzersDuringBuild>false</RunAnalyzersDuringBuild>
</PropertyGroup>

View File

@ -55,7 +55,7 @@ public class Program
.ConfigureWebHost(webHostBuilder =>
{
webHostBuilder.UseKestrel();
webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "FastTunnel.Hosting");
webHostBuilder.UseSetting(WebHostDefaults.HostingStartupAssembliesKey, "FastTunnel.Api;FastTunnel.Hosting");
webHostBuilder.ConfigureAppConfiguration((hostingContext, config) =>
{

View File

@ -1,16 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Project>
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<DeleteExistingFiles>false</DeleteExistingFiles>
<ExcludeApp_Data>false</ExcludeApp_Data>
<LaunchSiteAfterPublish>true</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net5.0\publish\</PublishUrl>
<PublishUrl>bin\Release\net6.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<_TargetId>Folder</_TargetId>
</PropertyGroup>
</Project>

View File

@ -1,20 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net5.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
<SiteUrlToLaunchAfterPublish />
<TargetFramework>net5.0</TargetFramework>
<ProjectGuid>def2e322-9075-4c3f-9967-7eaf0ee28ceb</ProjectGuid>
<SelfContained>false</SelfContained>
</PropertyGroup>
</Project>

View File

@ -1,16 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
https://go.microsoft.com/fwlink/?LinkID=208121.
-->
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<DeleteExistingFiles>False</DeleteExistingFiles>
<ExcludeApp_Data>False</ExcludeApp_Data>
<LaunchSiteAfterPublish>True</LaunchSiteAfterPublish>
<LastUsedBuildConfiguration>Release</LastUsedBuildConfiguration>
<LastUsedPlatform>Any CPU</LastUsedPlatform>
<PublishProvider>FileSystem</PublishProvider>
<PublishUrl>bin\Release\net5.0\publish\</PublishUrl>
<WebPublishMethod>FileSystem</WebPublishMethod>
</PropertyGroup>
</Project>

View File

@ -35,45 +35,6 @@ public class Startup
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
var serverOptions = Configuration.GetSection("FastTunnel").Get<DefaultServerConfig>();
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromSeconds(serverOptions.Api.JWT.ClockSkew),
ValidateIssuerSigningKey = true,
ValidAudience = serverOptions.Api.JWT.ValidAudience,
ValidIssuer = serverOptions.Api.JWT.ValidIssuer,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(serverOptions.Api.JWT.IssuerSigningKey))
};
options.Events = new JwtBearerEvents
{
OnChallenge = async context =>
{
context.HandleResponse();
context.Response.ContentType = "application/json;charset=utf-8";
context.Response.StatusCode = StatusCodes.Status200OK;
await context.Response.WriteAsync(new
{
errorCode = 1,
errorMessage = context.Error ?? "Token is Required"
}.ToJson());
},
};
});
services.AddAuthorization();
services.AddControllers();
#if DEBUG
services.AddSwaggerGen(c =>
{
@ -102,9 +63,15 @@ public class Startup
app.UseAuthorization();
// --------------------- Custom UI ----------------
app.UseFastTunnelServer();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapFallback(async (HttpContext ctx) =>
{
await ctx.Response.Body.WriteAsync(Encoding.UTF8.GetBytes("404~"));
});
});
}
}

View File

@ -1,12 +0,0 @@
{
"Logging": {
"LogLevel": {
// Trace Debug Information Warning Error
"Default": "Debug",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"EnableFileLog": false
}

View File

@ -1,50 +0,0 @@
{
"Logging": {
"LogLevel": {
// Trace Debug Information Warning Error
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
//
"EnableFileLog": false,
"FastTunnel": {
// Http&
"BasePort": 1270,
//
// SubDomain ${SubDomain}.${WebDomain}访访
"WebDomain": "test.cc",
// 访访ip
"WebAllowAccessIps": [ "192.168.0.101" ],
// Forward.false
"EnableForward": true,
// Tokenstoken
"Tokens": [ "TOKEN_FOR_CLIENT_AUTHENTICATION" ],
/**
* 访apiJWT
*/
"Api": {
"JWT": {
"ClockSkew": 10,
"ValidAudience": "https://suidao.io",
"ValidIssuer": "FastTunnel",
"IssuerSigningKey": "This is IssuerSigningKey",
"Expires": 120
},
"Accounts": [
{
"Name": "admin",
"Password": "admin123"
}
]
}
}
}

View File

@ -4,7 +4,7 @@ color 0e
@echo ==================================
@echo 提醒:请右键本文件,用管理员方式打开。
@echo ==================================
@echo Start Install FastTunnel.Server
@echo Start Install ./../FastTunnel.Server
sc create FastTunnel.Server binPath=%~dp0\FastTunnel.Server.exe start= auto
sc description FastTunnel.Server "FastTunnel-开源内网穿透服务仓库地址https://github.com/SpringHgui/FastTunnel star项目以支持作者"

View File

@ -4,7 +4,7 @@ color 0e
@echo ==================================
@echo 提醒:请右键本文件,用管理员方式打开。
@echo ==================================
@echo Start Remove FastTunnel.Server
@echo Start Remove ./../FastTunnel.Server
Net stop FastTunnel.Server
sc delete FastTunnel.Server