XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

LumaTunnel

【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。

公开
关注 0 Fork 0 Star 0
UTF-8
using LumaTunnel.Server.Core.Runtime;
using LumaTunnel.Server.Core.Services;
using XFEExtension.NetCore.ServerInteractive.Utilities.Extensions;
using XFEExtension.NetCore.ServerInteractive.Utilities.Server;

namespace LumaTunnel.Server;

public sealed partial class ServerWorker(ILogger<ServerWorker> logger) : BackgroundService
{
    private XFEServerCore? _serverCore;
    private ServerRuntime? _runtime;
    private FileSystemWatcher? _deviceWatcher;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _runtime = await ServerRuntime.InitializeAsync(cancellationToken: stoppingToken).ConfigureAwait(false);
        _deviceWatcher = new FileSystemWatcher(_runtime.Paths.DataDirectory, Path.GetFileName(_runtime.Paths.DevicesFile))
        {
            NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.CreationTime,
            EnableRaisingEvents = true
        };
        _deviceWatcher.Changed += OnDeviceFileChanged;
        _deviceWatcher.Created += OnDeviceFileChanged;
        _deviceWatcher.Renamed += OnDeviceFileChanged;

        _serverCore = XFEServerCoreBuilder.CreateBuilder()
            .AddVerifyService<ApiRateLimitVerifyService>()
            .AddOriginalService<TunnelGatewayOriginalService>()
            .AddService<HealthService>()
            .AddService<EnrollmentService>()
            .AddService<NodeStatusService>()
            .Build(options =>
            {
                options.AcceptGet = true;
                options.AcceptPost = true;
                options.AcceptNonStandardJson = false;
                options.MainEntryPoint = "api";
                options.ServerCoreName = "LumaTunnelServer";
                foreach (var url in _runtime.Settings.BindingUrls)
                    options.BindIP(url);
            });

        var server = XFEServerBuilder.CreateBuilder()
            .UseXFEServer()
            .AddServerCore(_serverCore)
            .Build();

        using var registration = stoppingToken.Register(() =>
        {
            try
            {
                _serverCore.CyberCommServer.StopCyberCommServer();
            }
            catch (Exception exception)
            {
                LogStopFailure(logger, exception);
            }
        });

        LogStarting(logger,
            _runtime.Settings.NodeName,
            _runtime.Settings.NodeId,
            string.Join(", ", _runtime.Settings.BindingUrls));

        var flushTask = FlushLoopAsync(stoppingToken);
        try
        {
            await server.Start().ConfigureAwait(false);
        }
        catch (Exception) when (stoppingToken.IsCancellationRequested)
        {
            LogStopped(logger);
        }
        await flushTask.ConfigureAwait(false);
    }

    public override async Task StopAsync(CancellationToken cancellationToken)
    {
        if (_runtime?.TunnelConnections is { } tunnels)
        {
            var shutdown = tunnels.DisposeAsync().AsTask();
            await Task.WhenAny(shutdown, Task.Delay(TimeSpan.FromSeconds(5), cancellationToken)).ConfigureAwait(false);
        }

        if (_runtime is not null)
            await _runtime.FlushAsync(cancellationToken).ConfigureAwait(false);

        _serverCore?.CyberCommServer.StopCyberCommServer();
        if (_deviceWatcher is not null)
        {
            _deviceWatcher.EnableRaisingEvents = false;
            _deviceWatcher.Dispose();
            _deviceWatcher = null;
        }
        await base.StopAsync(cancellationToken).ConfigureAwait(false);
    }

    private async void OnDeviceFileChanged(object sender, FileSystemEventArgs e)
    {
        try
        {
            await Task.Delay(50).ConfigureAwait(false);
            if (_runtime is not null)
                await _runtime.Devices.ReloadAsync().ConfigureAwait(false);
        }
        catch (IOException)
        {
        }
    }

    private async Task FlushLoopAsync(CancellationToken cancellationToken)
    {
        try
        {
            while (!cancellationToken.IsCancellationRequested)
            {
                await Task.Delay(TimeSpan.FromMinutes(1), cancellationToken).ConfigureAwait(false);
                if (_runtime is not null)
                    await _runtime.FlushAsync(cancellationToken).ConfigureAwait(false);
            }
        }
        catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
        {
        }
    }

    [LoggerMessage(1, LogLevel.Warning, "Failed to stop the HTTP.sys listener cleanly.")]
    private static partial void LogStopFailure(ILogger logger, Exception exception);

    [LoggerMessage(2, LogLevel.Information, "LumaTunnel node {NodeName} ({NodeId}) is starting on {Bindings}.")]
    private static partial void LogStarting(ILogger logger, string nodeName, string nodeId, string bindings);

    [LoggerMessage(3, LogLevel.Information, "LumaTunnel listener stopped during service shutdown.")]
    private static partial void LogStopped(ILogger logger);
}