clean file useless

This commit is contained in:
SpringHgui 2021-08-02 00:04:13 +08:00
parent 3e6a3bbc2a
commit ff32f44223
34 changed files with 271 additions and 1146 deletions

View File

@ -0,0 +1,244 @@
using FastTunnel.Core.Config;
using FastTunnel.Core.Models;
using System;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
using FastTunnel.Core.Extensions;
using System.Timers;
using System.Threading;
using Microsoft.Extensions.Logging;
using FastTunnel.Core.Handlers.Client;
using Microsoft.Extensions.Configuration;
using FastTunnel.Core.Server;
using FastTunnel.Core.Sockets;
using Microsoft.Extensions.Options;
using System.Net.WebSockets;
using System.Text.Json;
using FastTunnel.Core.Protocol;
namespace FastTunnel.Core.Client
{
public class FastTunnelClient : IFastTunnelClient
{
//Socket _client;
private IFastTunnelClientSocket socket;
protected ILogger<FastTunnelClient> _logger;
System.Timers.Timer timer_heart;
double heartInterval = 10 * 1000; // 10 秒心跳
public DateTime lastHeart;
int reTrySpan = 10 * 1000; // 登陆失败后重试间隔
HttpRequestHandler _newCustomerHandler;
NewForwardHandler _newSSHHandler;
LogHandler _logHandler;
ClientHeartHandler _clientHeartHandler;
Message<LogInMassage> loginMsg;
protected readonly IOptionsMonitor<DefaultClientConfig> _configuration;
private readonly CancellationTokenSource cancellationTokenSource = new();
public SuiDaoServer Server { get; protected set; }
public FastTunnelClient(
ILogger<FastTunnelClient> logger,
HttpRequestHandler newCustomerHandler,
NewForwardHandler newSSHHandler, LogHandler logHandler,
IOptionsMonitor<DefaultClientConfig> configuration,
ClientHeartHandler clientHeartHandler)
{
_logger = logger;
_newCustomerHandler = newCustomerHandler;
_newSSHHandler = newSSHHandler;
_logHandler = logHandler;
_clientHeartHandler = clientHeartHandler;
_configuration = configuration;
timer_heart = new System.Timers.Timer();
timer_heart.AutoReset = false;
timer_heart.Interval = heartInterval;
timer_heart.Elapsed += HeartElapsed;
}
private async Task reConnAsync()
{
Close();
do
{
try
{
Thread.Sleep(reTrySpan);
_logger.LogInformation("登录重试...");
socket = await loginAsync(CancellationToken.None);
break;
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
}
} while (true);
await connSuccessAsync();
}
private async void HeartElapsed(object sender, ElapsedEventArgs e)
{
timer_heart.Enabled = false;
try
{
socket.SendAsync(new Message<HeartMassage> { MessageType = MessageType.Heart, Content = null }, cancellationTokenSource.Token).Wait();
}
catch (Exception)
{
// 与服务端断开连接
await reConnAsync();
}
finally
{
timer_heart.Enabled = true;
}
}
/// <summary>
/// 启动客户端
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="customLoginMsg">自定义登录信息,可进行扩展业务</param>
public async Task StartAsync(CancellationToken cancellationToken)
{
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.cancellationTokenSource.Token);
_logger.LogInformation("===== FastTunnel Client Start =====");
try
{
socket = await loginAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
await reConnAsync();
return;
}
_ = connSuccessAsync();
}
protected virtual async Task<IFastTunnelClientSocket> loginAsync(CancellationToken cancellationToken)
{
Server = _configuration.CurrentValue.Server;
_logger.LogInformation($"正在连接服务端 {Server.ServerAddr}:{Server.ServerPort}");
try
{
// 连接到的目标IP
socket = new DefultClientSocket();
await socket.ConnectAsync(
new Uri($"ws://{_configuration.CurrentValue.Server.ServerAddr}:{_configuration.CurrentValue.Server.ServerPort}"), cancellationToken);
_logger.LogInformation("连接成功");
}
catch (Exception)
{
throw;
}
loginMsg = new Message<LogInMassage>
{
MessageType = MessageType.C_LogIn,
Content = new LogInMassage
{
Webs = _configuration.CurrentValue.Webs,
SSH = _configuration.CurrentValue.Forwards,
},
};
// 登录
await socket.SendAsync(loginMsg, cancellationToken);
return socket;
}
void Close()
{
timer_heart.Stop();
socket.CloseAsync();
}
private async Task connSuccessAsync()
{
_logger.LogDebug("通信已建立");
// 心跳开始
timer_heart.Start();
await ReceiveServerAsync(socket);
// await new PipeHepler(_client, ProceccLine).ProcessLinesAsync();
}
private async Task ReceiveServerAsync(IFastTunnelClientSocket client)
{
var tunnelProtocol = new TunnelProtocol();
byte[] buffer = new byte[512];
int n = 0;
try
{
while (true)
{
n = await client.ReceiveAsync(buffer, cancellationTokenSource.Token);
var cmds = tunnelProtocol.HandleBuffer(buffer, 0, n);
foreach (var item in cmds)
{
await HandleServerRequestAsync(item);
}
}
}
catch (Exception ex)
{
throw;
}
}
private async Task HandleServerRequestAsync(string lineCmd)
{
_logger.LogInformation($"服务端指令 {lineCmd}");
var cmds = lineCmd.Split("||");
var type = cmds[0];
TunnelMassage msg = null;
IClientHandler handler;
switch (type)
{
case "Heart":
handler = _clientHeartHandler;
break;
case "S_NewCustomer":
handler = _newCustomerHandler;
msg = JsonSerializer.Deserialize<NewCustomerMassage>(cmds[1]);
break;
case "S_NewSSH":
handler = _newSSHHandler;
msg = JsonSerializer.Deserialize<NewForwardMessage>(cmds[1]);
break;
case "Log":
handler = _logHandler;
msg = JsonSerializer.Deserialize<LogMassage>(cmds[1]);
break;
default:
throw new Exception($"未处理的消息:{lineCmd}");
}
await handler.HandlerMsgAsync(this, msg);
}
}
}

View File

@ -10,7 +10,6 @@ using System.Threading;
using Microsoft.Extensions.Logging;
using FastTunnel.Core.Handlers.Client;
using Microsoft.Extensions.Configuration;
using FastTunnel.Core.Server;
using FastTunnel.Core.Sockets;
using Microsoft.Extensions.Options;
using System.Net.WebSockets;
@ -21,11 +20,8 @@ namespace FastTunnel.Core.Client
{
public class FastTunnelClient : IFastTunnelClient
{
//Socket _client;
private IFastTunnelClientSocket socket;
protected ILogger<FastTunnelClient> _logger;
public DateTime lastHeart;
HttpRequestHandler _newCustomerHandler;
@ -63,17 +59,8 @@ namespace FastTunnel.Core.Client
CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, this.cancellationTokenSource.Token);
_logger.LogInformation("===== FastTunnel Client Start =====");
try
{
socket = await loginAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
return;
}
socket = await loginAsync(cancellationToken);
_logger.LogInformation($"通讯已建立");
await ReceiveServerAsync(socket);
}
@ -144,10 +131,6 @@ namespace FastTunnel.Core.Client
IClientHandler handler;
switch (type)
{
case "Heart":
handler = _clientHeartHandler;
msg = JsonSerializer.Deserialize<HeartMassage>(cmds[1]);
break;
case "S_NewCustomer":
handler = _newCustomerHandler;
msg = JsonSerializer.Deserialize<NewCustomerMassage>(cmds[1]);

View File

@ -1,7 +1,6 @@
using FastTunnel.Core.Config;
using FastTunnel.Core.Models;
using Microsoft.Extensions.Logging;
using FastTunnel.Core.Handlers.Server;
using System.Collections.Concurrent;
using System;
using FastTunnel.Core.Listener;
@ -18,8 +17,6 @@ namespace FastTunnel.Core.Client
{
public class FastTunnelServer
{
public ConcurrentDictionary<string, NewRequest> RequestTemp { get; private set; } = new ConcurrentDictionary<string, NewRequest>();
public ConcurrentDictionary<string, TaskCompletionSource<Stream>> ResponseTasks { get; } = new();
public ConcurrentDictionary<string, WebInfo> WebList { get; private set; } = new();
@ -28,8 +25,6 @@ namespace FastTunnel.Core.Client
= new ConcurrentDictionary<int, ForwardInfo<ForwardHandlerArg>>();
readonly ILogger _logger;
//readonly ClientListenerV2 clientListener;
//readonly HttpListenerV2 http_listener;
public readonly IOptionsMonitor<DefaultServerConfig> serverOption;
public IProxyConfigProvider proxyConfig;

View File

@ -6,8 +6,6 @@ namespace FastTunnel.Core.Config
{
public interface IServerConfig
{
// int BindPort { get; set; }
#region Web相关配置
int WebProxyPort { get; set; }

View File

@ -1,146 +0,0 @@
using FastTunnel.Core.Client;
using FastTunnel.Core.Config;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Handlers;
using FastTunnel.Core.Handlers.Server;
using FastTunnel.Core.Models;
using FastTunnel.Core.Server;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Net.Sockets;
using System.Text;
namespace FastTunnel.Core.Dispatchers
{
public class ClientDispatcher : IListenerDispatcher
{
readonly ILogger _logger;
readonly IServerConfig _serverSettings;
readonly FastTunnelServer _fastTunnelServer;
readonly LoginHandler _loginHandler;
readonly HeartMessageHandler _heartHandler;
readonly SwapMessageHandler _swapMsgHandler;
Action<Socket> offLineAction;
public ClientDispatcher(FastTunnelServer fastTunnelServer, ILogger logger, IServerConfig serverSettings)
{
_logger = logger;
_serverSettings = serverSettings;
_fastTunnelServer = fastTunnelServer;
_loginHandler = new LoginHandler(logger);
_heartHandler = new HeartMessageHandler();
_swapMsgHandler = new SwapMessageHandler(logger);
}
string temp = string.Empty;
public void Dispatch(Socket client)
{
var reader = new DataReciver(client);
reader.OnComplete += Reader_OnComplete;
reader.OnError += Reader_OnError;
reader.OnReset += Reader_OnReset;
reader.ReciveOneAsync();
}
private void Reader_OnReset(DataReciver send, Socket socket, SocketAsyncEventArgs e)
{
offLineAction(socket);
}
private void Reader_OnError(DataReciver send, SocketAsyncEventArgs e)
{
_logger.LogError("接收客户端数据异常 {0}", e.SocketError);
}
private void Reader_OnComplete(DataReciver reader, byte[] buffer, int offset, int count)
{
var words = Encoding.UTF8.GetString(buffer, offset, count);
words += temp;
temp = string.Empty;
_logger.LogDebug($"revice from client: {words}");
try
{
int index = 0;
bool needRecive = false;
while (true)
{
var firstIndex = words.IndexOf("\n");
if (firstIndex < 0)
{
temp += words;
reader.ReciveOneAsync();
break;
}
var sub_words = words.Substring(index, firstIndex + 1);
var res = handle(sub_words, reader.Socket);
if (res.NeedRecive)
needRecive = true;
words = words.Replace(sub_words, string.Empty);
if (string.IsNullOrEmpty(words))
break;
}
if (needRecive)
{
reader.ReciveOneAsync();
}
}
catch (Exception ex)
{
_logger.LogError(ex);
_logger.LogError($"handle fail msg{words}");
// throw;
reader.Socket.Send(new Message<LogMassage>() { MessageType = MessageType.Log, Content = new LogMassage(LogMsgType.Error, ex.Message) });
reader.ReciveOneAsync();
}
}
private IClientMessageHandler handle(string words, Socket client)
{
Message<JObject> msg = JsonConvert.DeserializeObject<Message<JObject>>(words);
IClientMessageHandler handler = null;
switch (msg.MessageType)
{
case MessageType.C_LogIn: // 登录
handler = _loginHandler;
break;
case MessageType.Heart: // 心跳
handler = _heartHandler;
break;
case MessageType.C_SwapMsg: // 交换数据
handler = _swapMsgHandler;
break;
default:
throw new Exception($"未知的通讯指令 {msg.MessageType}");
}
handler.HandlerMsg(this._fastTunnelServer, client, msg);
return handler;
}
public void Dispatch(Socket httpClient, Action<Socket> onOffLine)
{
offLineAction = onOffLine;
Dispatch(httpClient);
}
public void Dispatch(AsyncUserToken token, string words)
{
throw new NotImplementedException();
}
}
}

View File

@ -1,179 +0,0 @@
using FastTunnel.Core.Config;
using FastTunnel.Core.Client;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Models;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Net.Http;
using System.IO;
using FastTunnel.Core.Server;
using System.Diagnostics;
using Microsoft.Extensions.Options;
namespace FastTunnel.Core.Dispatchers
{
public class HttpDispatcherV2 : IListenerDispatcher
{
readonly ILogger _logger;
readonly IOptionsMonitor<DefaultServerConfig> _serverOption;
readonly FastTunnelServer _fastTunnelServer;
public HttpDispatcherV2(FastTunnelServer fastTunnelServer, ILogger logger, IOptionsMonitor<DefaultServerConfig> serverSettings)
{
_logger = logger;
_serverOption = serverSettings;
_fastTunnelServer = fastTunnelServer;
}
static string pattern = @"[hH]ost:.+";
public void Dispatch(AsyncUserToken token, string words)
{
_logger.LogDebug($"=======Dispatch HTTP {token.RequestId}========");
Stopwatch sw = new Stopwatch();
sw.Start();
// 1.检查白名单
try
{
var endpoint = token.Socket.RemoteEndPoint as System.Net.IPEndPoint;
_logger.LogInformation($"Receive HTTP Request {endpoint.Address}:{endpoint.Port}");
if (_serverOption.CurrentValue.WebAllowAccessIps != null)
{
if (!_serverOption.CurrentValue.WebAllowAccessIps.Contains(endpoint.Address.ToString()))
{
HandlerHostNotAccess(token.Socket);
return;
}
}
}
catch (Exception ex)
{
_logger.LogError(ex);
}
string Host;
MatchCollection collection = Regex.Matches(words, pattern);
if (collection.Count == 0)
{
_logger.LogError($"【Host异常】{words}");
// 返回错误页
HandlerHostRequired(token.Socket);
return;
}
else
{
Host = collection[0].Value;
}
var domain = Host.Split(":")[1].Trim();
_logger.LogDebug($"=======Dispatch domain:{domain} {token.RequestId} ========");
// 判断是否为ip
if (IsIpDomian(domain))
{
// 返回错误页
HandlerHostRequired(token.Socket);
return;
}
WebInfo web;
if (!_fastTunnelServer.WebList.TryGetValue(domain, out web))
{
_logger.LogDebug($"=======站点未登录 {token.RequestId}========");
HandlerClientNotOnLine(token.Socket, domain);
return;
}
try
{
// 发送指令给客户端,等待建立隧道
web.Socket.SendCmd(new Message<NewCustomerMassage> { MessageType = MessageType.S_NewCustomer, Content = new NewCustomerMassage { MsgId = token.RequestId, WebConfig = web.WebConfig } });
}
catch (Exception)
{
_logger.LogDebug($"[客户端不在线] {token.RequestId}");
HandlerClientNotOnLine(token.Socket, domain);
// 移除
_fastTunnelServer.WebList.TryRemove(domain, out _);
}
// 将等待的http请求
_fastTunnelServer.RequestTemp.TryAdd(token.RequestId, new NewRequest
{
CustomerClient = token.Socket,
Buffer = token.Recived
});
}
public void Dispatch(Socket httpClient)
{
throw new NotImplementedException();
}
private bool IsIpDomian(string domain)
{
return Regex.IsMatch(domain, @"^\d.\d.\d.\d.\d$");
}
private void HandlerHostNotAccess(Socket client)
{
_logger.LogDebug($"### NotAccessIps:'{client.RemoteEndPoint}'");
string statusLine = "HTTP/1.1 200 OK\r\n";
string responseHeader = "Content-Type: text/html\r\n";
byte[] responseBody = Encoding.UTF8.GetBytes(TunnelResource.Page_NotAccessIps);
client.Send(Encoding.UTF8.GetBytes(statusLine));
client.Send(Encoding.UTF8.GetBytes(responseHeader));
client.Send(Encoding.UTF8.GetBytes("\r\n"));
client.Send(responseBody);
client.Close();
}
private void HandlerHostRequired(Socket client)
{
_logger.LogDebug($"### HostRequired:'{client.RemoteEndPoint}'");
string statusLine = "HTTP/1.1 200 OK\r\n";
string responseHeader = "Content-Type: text/html\r\n";
byte[] responseBody = Encoding.UTF8.GetBytes(TunnelResource.Page_HostRequired);
client.Send(Encoding.UTF8.GetBytes(statusLine));
client.Send(Encoding.UTF8.GetBytes(responseHeader));
client.Send(Encoding.UTF8.GetBytes("\r\n"));
client.Send(responseBody);
client.Close();
}
private void HandlerClientNotOnLine(Socket client, string domain)
{
_logger.LogDebug($"### TunnelNotFound:'{domain}'");
string statusLine = "HTTP/1.1 200 OK\r\n";
string responseHeader = "Content-Type: text/html\r\n";
byte[] responseBody = Encoding.UTF8.GetBytes(TunnelResource.Page_NoTunnel);
client.Send(Encoding.UTF8.GetBytes(statusLine));
client.Send(Encoding.UTF8.GetBytes(responseHeader));
client.Send(Encoding.UTF8.GetBytes("\r\n"));
client.Send(responseBody);
client.Close();
}
public void Dispatch(Socket httpClient, Action<Socket> onOffLine)
{
Dispatch(httpClient);
}
}
}

View File

@ -1,16 +0,0 @@
using FastTunnel.Core.Server;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace FastTunnel.Core.Dispatchers
{
public interface IListenerDispatcher
{
void Dispatch(AsyncUserToken token, string words);
void DispatchAsync(Socket httpClient);
}
}

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace FastTunnel.Core.Extensions
{
public static class ByteArrayExtension
public static class ByteArrayExtensions
{
public static string GetString(this byte[] buffer, int offset, int count)
{

View File

@ -5,7 +5,7 @@ using System.Text;
namespace FastTunnel.Core.Extensions
{
public static class LogExtentions
public static class LoggerExtentions
{
public static void LogError(this ILogger logger, Exception ex)
{

View File

@ -6,7 +6,7 @@ using System.Text;
namespace FastTunnel.Core.Extensions
{
public static class SocketExtension
public static class SocketExtensions
{
public static void SendCmd<T>(this Socket socket, Message<T> message)
where T : TunnelMassage

View File

@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace FastTunnel.Core.Extensions
{
public static class WebSocketExtension
public static class WebSocketExtensions
{
public static async Task SendCmdAsync<T>(this WebSocket socket, Message<T> message,
WebSocketMessageType webSocketMessage, bool end, CancellationToken cancellationToken)

View File

@ -26,9 +26,6 @@
<Compile Remove="Client\SuiDaoServer.cs.BASE.cs" />
<Compile Remove="Client\SuiDaoServer.cs.LOCAL.cs" />
<Compile Remove="Client\SuiDaoServer.cs.REMOTE.cs" />
<Compile Remove="Dispatchers\ClientDispatcher.cs" />
<Compile Remove="Dispatchers\HttpDispatcher.cs" />
<Compile Remove="Dispatchers\HttpDispatcherV2.cs" />
<Compile Remove="Handlers\Server\SwapMessageHandler.cs" />
<Compile Remove="Listener.cs" />
<Compile Remove="Listener\ClientListener.cs" />
@ -42,7 +39,6 @@
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0-preview.6.21352.12" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="5.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="5.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.16-alpha" />
<PackageReference Include="Yarp.ReverseProxy" Version="1.0.0-preview.12.21328.2" />
</ItemGroup>

View File

@ -2,7 +2,6 @@
using FastTunnel.Core.Dispatchers;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Models;
using FastTunnel.Core.Server;
using Microsoft.AspNetCore.Hosting.Server;
using System;
using System.Collections.Generic;
@ -15,13 +14,13 @@ using System.Threading.Tasks;
namespace FastTunnel.Core.Dispatchers
{
public class ForwardDispatcher : IListenerDispatcher
public class ForwardHandler
{
private FastTunnelServer _server;
private WebSocket _client;
private ForwardConfig _config;
public ForwardDispatcher(FastTunnelServer server, WebSocket client, ForwardConfig config)
public ForwardHandler(FastTunnelServer server, WebSocket client, ForwardConfig config)
{
_server = server;
_client = client;
@ -46,10 +45,5 @@ namespace FastTunnel.Core.Dispatchers
{
}
}
public void Dispatch(AsyncUserToken token, string words)
{
throw new NotImplementedException();
}
}
}

View File

@ -15,7 +15,6 @@ using System.Net.WebSockets;
using FastTunnel.Core.Forwarder;
using Microsoft;
using Microsoft.AspNetCore.DataProtection;
using FastTunnel.Core.Server;
using System.Data.Common;
namespace FastTunnel.Core.Handlers.Client

View File

@ -1,24 +0,0 @@
using FastTunnel.Core.Client;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Models;
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Net.WebSockets;
using System.Text;
using System.Threading.Tasks;
namespace FastTunnel.Core.Handlers.Server
{
public class HeartMessageHandler : IClientMessageHandler
{
public bool NeedRecive => true;
public async Task<bool> HandlerMsg<T>(FastTunnelServer server, WebSocket client, T msg)
where T : TunnelMassage
{
await client.SendCmdAsync(new Message<HeartMassage>() { MessageType = MessageType.Heart, Content = new HeartMassage { } });
return NeedRecive;
}
}
}

View File

@ -3,7 +3,6 @@ using FastTunnel.Core.Dispatchers;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Filters;
using FastTunnel.Core.Global;
using FastTunnel.Core.Handlers.Server;
using FastTunnel.Core.Listener;
using FastTunnel.Core.Models;
using Microsoft.Extensions.Logging;
@ -94,7 +93,7 @@ namespace FastTunnel.Core.Handlers
var ls = new PortProxyListener("0.0.0.0", item.RemotePort, _logger);
ls.Start(new ForwardDispatcher(server, client, item));
ls.Start(new ForwardHandler(server, client, item));
// listen success
server.ForwardList.TryAdd(item.RemotePort, new ForwardInfo<ForwardHandlerArg> { Listener = ls, Socket = client, SSHConfig = item });

View File

@ -1,67 +0,0 @@
using FastTunnel.Core.Dispatchers;
using FastTunnel.Core.Handlers.Server;
using FastTunnel.Core.Server;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace FastTunnel.Core.Listener
{
public class HttpListenerV2
{
ILogger _logger;
public string ListenIp { get; protected set; }
public int ListenPort { get; protected set; }
IListenerDispatcher _requestDispatcher;
public IList<Socket> ConnectedSockets = new List<Socket>();
Server.Server server;
public HttpListenerV2(string ip, int port, ILogger logger)
{
_logger = logger;
this.ListenIp = ip;
this.ListenPort = port;
server = new Server.Server(1000, 512, true, _logger);
}
public void Start(IListenerDispatcher requestDispatcher, int backlog = 100)
{
_requestDispatcher = requestDispatcher;
IPAddress ipa = IPAddress.Parse(ListenIp);
IPEndPoint localEndPoint = new IPEndPoint(ipa, ListenPort);
server.Init();
server.Start(localEndPoint, "\r\n\r\n", handle);
_logger.LogInformation($"监听客户端 -> {ListenIp}:{ListenPort}");
}
private bool handle(AsyncUserToken token, string words)
{
try
{
_requestDispatcher.Dispatch(token, words);
return false;
}
catch (Exception ex)
{
_logger.LogCritical(ex, $"【处理HTTP请求异常】{words}");
}
return false;
}
public void Stop()
{
}
}
}

View File

@ -1,5 +1,4 @@
using FastTunnel.Core.Dispatchers;
using FastTunnel.Core.Handlers.Server;
using System;
using System.Collections.Generic;
using System.Net.Sockets;

View File

@ -1,5 +1,4 @@
using FastTunnel.Core.Dispatchers;
using FastTunnel.Core.Handlers.Server;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
@ -20,7 +19,7 @@ namespace FastTunnel.Core.Listener
int m_numConnectedSockets;
bool shutdown = false;
IListenerDispatcher _requestDispatcher;
ForwardHandler _requestDispatcher;
Socket listenSocket;
public IList<Socket> ConnectedSockets = new List<Socket>();
@ -37,7 +36,7 @@ namespace FastTunnel.Core.Listener
listenSocket.Bind(localEndPoint);
}
public void Start(IListenerDispatcher requestDispatcher)
public void Start(ForwardHandler requestDispatcher)
{
shutdown = false;
_requestDispatcher = requestDispatcher;

View File

@ -7,8 +7,8 @@ namespace FastTunnel.Core.Models
{
public class ForwardHandlerArg
{
public ForwardConfig SSHConfig { get; internal set; }
public ForwardConfig SSHConfig { get; set; }
public Socket LocalClient { get; internal set; }
public Socket LocalClient { get; set; }
}
}

View File

@ -1,11 +0,0 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace FastTunnel.Core.Models
{
public class HeartMassage : TunnelMassage
{
public string Time { get; set; }
}
}

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
namespace FastTunnel.Core.Models
{
public class NewRequest
{
public Socket CustomerClient { get; set; }
public byte[] Buffer { get; set; }
}
}

View File

@ -1,7 +1,6 @@
using FastTunnel.Core.Client;
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Handlers;
using FastTunnel.Core.Handlers.Server;
using FastTunnel.Core.Protocol;
using Microsoft.Extensions.Logging;
using System;
@ -20,8 +19,6 @@ namespace FastTunnel.Core.Models
public class TunnelClient
{
readonly LoginHandler _loginHandler;
readonly HeartMessageHandler _heartHandler;
//readonly SwapMessageHandler _swapMsgHandler;
FastTunnelServer fastTunnelServer;
ILogger logger;
WebSocket webSocket;
@ -32,8 +29,6 @@ namespace FastTunnel.Core.Models
this.logger = logger;
this.fastTunnelServer = fastTunnelServer;
this._loginHandler = new LoginHandler(logger, fastTunnelServer.proxyConfig);
this._heartHandler = new HeartMessageHandler();
// this._swapMsgHandler = new SwapMessageHandler(logger);
}
public async Task ReviceAsync()
@ -45,6 +40,8 @@ namespace FastTunnel.Core.Models
{
var res = await webSocket.ReceiveAsync(buffer, CancellationToken.None);
var cmds = tunnelProtocol.HandleBuffer(buffer, 0, res.Count);
if (cmds == null) continue;
foreach (var item in cmds)
{
if (!await HandleCmdAsync(webSocket, item))
@ -71,9 +68,6 @@ namespace FastTunnel.Core.Models
handler = _loginHandler;
msg = JsonSerializer.Deserialize<LogInMassage>(cmds[1]);
break;
case "Heart": // 心跳
handler = _heartHandler;
break;
default:
throw new Exception($"未知的通讯指令 {lineCmd}");
}

View File

@ -1,20 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace FastTunnel.Core.Server
{
public class AsyncUserToken
{
public Socket Socket { get; set; }
public string MassgeTemp { get; set; }
public byte[] Recived { get; set; }
public string RequestId { get; set; }
}
}

View File

@ -1,69 +0,0 @@
// This class creates a single large buffer which can be divided up
// and assigned to SocketAsyncEventArgs objects for use with each
// socket I/O operation.
// This enables bufffers to be easily reused and guards against
// fragmenting heap memory.
//
// The operations exposed on the BufferManager class are not thread safe.
using System.Collections.Generic;
using System.Net.Sockets;
namespace FastTunnel.Core.Server
{
class BufferManager
{
int m_numBytes; // the total number of bytes controlled by the buffer pool
byte[] m_buffer; // the underlying byte array maintained by the Buffer Manager
Stack<int> m_freeIndexPool; //
int m_currentIndex;
int m_bufferSize;
public BufferManager(int totalBytes, int bufferSize)
{
m_numBytes = totalBytes;
m_currentIndex = 0;
m_bufferSize = bufferSize;
m_freeIndexPool = new Stack<int>();
}
// Allocates buffer space used by the buffer pool
public void InitBuffer()
{
// create one big large buffer and divide that
// out to each SocketAsyncEventArg object
m_buffer = new byte[m_numBytes];
}
// Assigns a buffer from the buffer pool to the
// specified SocketAsyncEventArgs object
//
// <returns>true if the buffer was successfully set, else false</returns>
public bool SetBuffer(SocketAsyncEventArgs args)
{
if (m_freeIndexPool.Count > 0)
{
args.SetBuffer(m_buffer, m_freeIndexPool.Pop(), m_bufferSize);
}
else
{
if ((m_numBytes - m_bufferSize) < m_currentIndex)
{
return false;
}
args.SetBuffer(m_buffer, m_currentIndex, m_bufferSize);
m_currentIndex += m_bufferSize;
}
return true;
}
// Removes the buffer from a SocketAsyncEventArg object.
// This frees the buffer back to the buffer pool
public void FreeBuffer(SocketAsyncEventArgs args)
{
m_freeIndexPool.Push(args.Offset);
args.SetBuffer(null, 0, 0);
}
}
}

View File

@ -1,115 +0,0 @@
using System;
using System.Buffers;
using System.Collections.Generic;
using System.IO.Pipelines;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;
namespace FastTunnel.Core.Server
{
public class PipeHepler
{
Socket m_socket;
Func<Socket, byte[], bool> processLine;
const int minimumBufferSize = 512;
public PipeHepler(Socket socket, Func<Socket, byte[], bool> processLine)
{
this.processLine = processLine;
m_socket = socket;
}
public async Task ProcessLinesAsync()
{
var pipe = new Pipe();
Task writing = FillPipeAsync(pipe.Writer);
Task reading = ReadPipeAsync(pipe.Reader);
await Task.WhenAll(reading, writing);
}
private async Task FillPipeAsync(PipeWriter writer)
{
while (true)
{
// Allocate at least 512 bytes from the PipeWriter.
Memory<byte> memory = writer.GetMemory(minimumBufferSize);
try
{
int bytesRead = await m_socket.ReceiveAsync(memory, SocketFlags.None);
if (bytesRead == 0)
{
break;
}
// Tell the PipeWriter how much was read from the Socket.
writer.Advance(bytesRead);
}
catch (Exception)
{
break;
}
// Make the data available to the PipeReader.
FlushResult result = await writer.FlushAsync();
if (result.IsCompleted)
{
break;
}
}
// By completing PipeWriter, tell the PipeReader that there's no more data coming.
await writer.CompleteAsync();
}
private async Task ReadPipeAsync(PipeReader reader)
{
while (true)
{
ReadResult result = await reader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
while (TryReadLine(ref buffer, out ReadOnlySequence<byte> line))
{
// Process the line.
if (!processLine(m_socket, line.ToArray()))
{
// 停止继续监听
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();
}
static 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)
{
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,323 +0,0 @@
// Implements the connection logic for the socket server.
// After accepting a connection, all data read from the client
// is sent back to the client. The read and echo back to the client pattern
// is continued until the client disconnects.
using FastTunnel.Core.Extensions;
using FastTunnel.Core.Utility.Extensions;
using Microsoft.Extensions.Logging;
using System;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
namespace FastTunnel.Core.Server
{
public class Server
{
private int m_numConnections; // the maximum number of connections the sample is designed to handle simultaneously
private int m_receiveBufferSize;// buffer size to use for each socket I/O operation
BufferManager m_bufferManager; // represents a large reusable set of buffers for all socket operations
const int opsToPreAlloc = 2; // read, write (don't alloc buffer space for accepts)
Socket listenSocket; // the socket used to listen for incoming connection requests
// pool of reusable SocketAsyncEventArgs objects for write, read and accept socket operations
SocketAsyncEventArgsPool m_readWritePool;
//int m_totalBytesRead; // counter of the total # bytes received by the server
int m_numConnectedSockets; // the total number of clients connected to the server
Semaphore m_maxNumberAcceptedClients;
Func<AsyncUserToken, string, bool> m_handller;
string m_sectionFlag;
IPEndPoint _localEndPoint;
bool m_isHttpServer;
ILogger _logger;
// Create an uninitialized server instance.
// To start the server listening for connection requests
// call the Init method followed by Start method
//
// <param name="numConnections">the maximum number of connections the sample is designed to handle simultaneously</param>
// <param name="receiveBufferSize">buffer size to use for each socket I/O operation</param>
public Server(int numConnections, int receiveBufferSize, bool isHttpServer, ILogger logger)
{
m_isHttpServer = isHttpServer;
_logger = logger;
//m_totalBytesRead = 0;
m_numConnectedSockets = 0;
m_numConnections = numConnections;
m_receiveBufferSize = receiveBufferSize;
// allocate buffers such that the maximum number of sockets can have one outstanding read and
// write posted to the socket simultaneously
m_bufferManager = new BufferManager(receiveBufferSize * numConnections * opsToPreAlloc,
receiveBufferSize);
m_readWritePool = new SocketAsyncEventArgsPool(numConnections);
m_maxNumberAcceptedClients = new Semaphore(numConnections, numConnections);
}
// Initializes the server by preallocating reusable buffers and
// context objects. These objects do not need to be preallocated
// or reused, but it is done this way to illustrate how the API can
// easily be used to create reusable objects to increase server performance.
//
public void Init()
{
// Allocates one large byte buffer which all I/O operations use a piece of. This gaurds
// against memory fragmentation
m_bufferManager.InitBuffer();
// preallocate pool of SocketAsyncEventArgs objects
SocketAsyncEventArgs readWriteEventArg;
for (int i = 0; i < m_numConnections; i++)
{
//Pre-allocate a set of reusable SocketAsyncEventArgs
readWriteEventArg = new SocketAsyncEventArgs();
readWriteEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(IO_Completed);
readWriteEventArg.UserToken = new AsyncUserToken();
// assign a byte buffer from the buffer pool to the SocketAsyncEventArg object
m_bufferManager.SetBuffer(readWriteEventArg);
// add SocketAsyncEventArg to the pool
m_readWritePool.Push(readWriteEventArg);
}
}
// Starts the server such that it is listening for
// incoming connection requests.
//
// <param name="localEndPoint">The endpoint which the server will listening
// for connection requests on</param>
public void Start(IPEndPoint localEndPoint, string sectionFlag, Func<AsyncUserToken, string, bool> handller)
{
m_handller = handller;
m_sectionFlag = sectionFlag;
_localEndPoint = localEndPoint;
// create the socket which listens for incoming connections
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
// start the server with a listen backlog of 100 connections
listenSocket.Listen();
// post accepts on the listening socket
StartAccept(null);
}
// Begins an operation to accept a connection request from the client
//
// <param name="acceptEventArg">The context object to use when issuing
// the accept operation on the server's listening socket</param>
public void StartAccept(SocketAsyncEventArgs acceptEventArg)
{
if (acceptEventArg == null)
{
acceptEventArg = new SocketAsyncEventArgs();
acceptEventArg.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
// socket must be cleared since the context object is being reused
acceptEventArg.AcceptSocket = null;
}
m_maxNumberAcceptedClients.WaitOne();
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArg);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArg);
}
}
// This method is the callback method associated with Socket.AcceptAsync
// operations and is invoked when an accept operation is complete
void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs e)
{
ProcessAccept(e);
}
private void ProcessAccept(SocketAsyncEventArgs e)
{
Interlocked.Increment(ref m_numConnectedSockets);
_logger.LogInformation($"[当前连接数]:{_localEndPoint.Port} | {m_numConnectedSockets}");
// Get the socket for the accepted client connection and put it into the
// ReadEventArg object user token
SocketAsyncEventArgs readEventArgs = m_readWritePool.Pop();
var token = readEventArgs.UserToken as AsyncUserToken;
token.Socket = e.AcceptSocket;
token.MassgeTemp = null;
token.Recived = null;
// 客户端请求不需要分配msgid
if (m_isHttpServer)
{
token.RequestId = $"{DateTime.Now.GetChinaTicks()}_{Guid.NewGuid().ToString().Replace("-", string.Empty)}";
_logger.LogDebug($"Accept {token.RequestId}");
}
// As soon as the client is connected, post a receive to the connection
bool willRaiseEvent = e.AcceptSocket.ReceiveAsync(readEventArgs);
if (!willRaiseEvent)
{
ProcessReceive(readEventArgs);
}
// Accept the next connection request
StartAccept(e);
}
// This method is called whenever a receive or send operation is completed on a socket
//
// <param name="e">SocketAsyncEventArg associated with the completed receive operation</param>
void IO_Completed(object sender, SocketAsyncEventArgs e)
{
// determine which type of operation just completed and call the associated handler
switch (e.LastOperation)
{
case SocketAsyncOperation.Receive:
ProcessReceive(e);
break;
case SocketAsyncOperation.Send:
ProcessSend(e);
break;
default:
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
// This method is invoked when an asynchronous receive operation completes.
// If the remote host closed the connection, then the socket is closed.
// If data was received then the data is echoed back to the client.
//
private void ProcessReceive(SocketAsyncEventArgs e)
{
AsyncUserToken token = (AsyncUserToken)e.UserToken;
_logger.LogDebug($"[ProcessReceive]: {_localEndPoint.Port} | {token.RequestId}");
if (e.BytesTransferred > 0 && e.SocketError == SocketError.Success)
{
bool needRecive = false;
var words = e.Buffer.GetString(e.Offset, e.BytesTransferred);
var sum = token.MassgeTemp + words;
// 只有http请求需要对已发送字节进行存储
if (m_isHttpServer)
{
if (token.Recived != null)
{
byte[] resArr = new byte[token.Recived.Length + e.BytesTransferred];
token.Recived.CopyTo(resArr, 0);
Array.Copy(e.Buffer, e.Offset, resArr, token.Recived.Length, e.BytesTransferred);
token.Recived = resArr;
}
else
{
byte[] resArr = new byte[e.BytesTransferred];
Array.Copy(e.Buffer, e.Offset, resArr, 0, e.BytesTransferred);
token.Recived = resArr;
}
}
if (sum.Contains(m_sectionFlag))
{
var array = (sum).Split(m_sectionFlag);
token.MassgeTemp = null;
var fullMsg = words.EndsWith(m_sectionFlag);
if (!fullMsg)
{
token.MassgeTemp = array[array.Length - 1];
}
for (int i = 0; i < array.Length - 1; i++)
{
needRecive = m_handller(token, array[i]);
if (needRecive)
{
continue;
}
else
{
// ÊÍ·Å×ÊÔ´
release(e);
return;
}
}
}
else
{
token.MassgeTemp = sum;
}
bool willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
// This method is invoked when an asynchronous send operation completes.
// The method issues another receive on the socket to read any additional
// data sent from the client
//
// <param name="e"></param>
private void ProcessSend(SocketAsyncEventArgs e)
{
if (e.SocketError == SocketError.Success)
{
// done echoing data back to the client
AsyncUserToken token = (AsyncUserToken)e.UserToken;
// read the next block of data send from the client
e.SetBuffer(e.Offset, m_receiveBufferSize);
bool willRaiseEvent = token.Socket.ReceiveAsync(e);
if (!willRaiseEvent)
{
ProcessReceive(e);
}
}
else
{
CloseClientSocket(e);
}
}
private void CloseClientSocket(SocketAsyncEventArgs e)
{
AsyncUserToken token = e.UserToken as AsyncUserToken;
// close the socket associated with the client
try
{
token.Socket.Shutdown(SocketShutdown.Send);
}
// throws if client process has already closed
catch (Exception) { }
token.Socket.Close();
release(e);
}
private void release(SocketAsyncEventArgs e)
{
// decrement the counter keeping track of the total number of clients connected to the server
Interlocked.Decrement(ref m_numConnectedSockets);
_logger.LogInformation($"[SocketCount]:{_localEndPoint.Port} | {m_numConnectedSockets}");
// Free the SocketAsyncEventArg so they can be reused by another client
m_readWritePool.Push(e);
m_maxNumberAcceptedClients.Release();
}
}
}

View File

@ -1,49 +0,0 @@
// Represents a collection of reusable SocketAsyncEventArgs objects.
using System;
using System.Collections.Generic;
using System.Net.Sockets;
class SocketAsyncEventArgsPool
{
Stack<SocketAsyncEventArgs> m_pool;
// Initializes the object pool to the specified size
//
// The "capacity" parameter is the maximum number of
// SocketAsyncEventArgs objects the pool can hold
public SocketAsyncEventArgsPool(int capacity)
{
m_pool = new Stack<SocketAsyncEventArgs>(capacity);
}
// Add a SocketAsyncEventArg instance to the pool
//
//The "item" parameter is the SocketAsyncEventArgs instance
// to add to the pool
public void Push(SocketAsyncEventArgs item)
{
if (item == null) { throw new ArgumentNullException("Items added to a SocketAsyncEventArgsPool cannot be null"); }
lock (m_pool)
{
m_pool.Push(item);
}
}
// Removes a SocketAsyncEventArgs instance from the pool
// and returns the object removed from the pool
public SocketAsyncEventArgs Pop()
{
lock (m_pool)
{
return m_pool.Pop();
}
}
// The number of SocketAsyncEventArgs instances in the pool
public int Count
{
get { return m_pool.Count; }
}
}

View File

@ -26,12 +26,20 @@ namespace FastTunnel.Core.Services
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
}
public Task StartAsync(CancellationToken cancellationToken)
public async Task StartAsync(CancellationToken cancellationToken)
{
return Task.Run(() =>
while (!cancellationToken.IsCancellationRequested)
{
_fastTunnelClient.StartAsync(cancellationToken);
}, cancellationToken);
try
{
await _fastTunnelClient.StartAsync(cancellationToken);
}
catch (Exception ex)
{
_logger.LogError(ex.Message);
await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
}
}
}
public Task StopAsync(CancellationToken cancellationToken)

View File

@ -1,27 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace FastTunnel.Core.Helper
{
public static class FileHelper
{
public static byte[] GetBytesFromFile(string fileName)
{
if (!File.Exists(fileName))
{
return null;
}
FileInfo fi = new FileInfo(fileName);
byte[] buff = new byte[fi.Length];
FileStream fs = fi.OpenRead();
fs.Read(buff, 0, Convert.ToInt32(fs.Length));
fs.Close();
return buff;
}
}
}

View File

@ -1,16 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace FastTunnel.Core.Helper
{
public static class ValidateHelper
{
public static string[] ValidateObject(object instance)
{
throw new NotImplementedException();
}
}
}

View File

@ -16,7 +16,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\FastTunel.Core.WebApi\FastTunel.Core.WebApi.csproj" />
<ProjectReference Include="..\FastTunnel.Core\FastTunnel.Core.csproj" />
</ItemGroup>

View File

@ -11,8 +11,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastTunnel.Server", "FastTu
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastTunel.Core.WebApi", "FastTunel.Core.WebApi\FastTunel.Core.WebApi.csproj", "{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@ -31,17 +29,12 @@ Global
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DEF2E322-9075-4C3F-9967-7EAF0EE28CEB}.Release|Any CPU.Build.0 = Release|Any CPU
{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{C8ADFEB1-59DB-4CE3-8D04-5B547107BCCB} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}
{79B1CA3F-D9E9-45F2-8A50-72084B41A0E6} = {0E2A9DA2-26AE-4657-B4C5-3A913E2F5A3C}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {3D9C6B44-6706-4EE8-9043-802BBE474A2E}