LumaTunnel
【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。
关注
0
Fork
0
Star
0
using System.Diagnostics;
using System.IO.Pipes;
using LumaTunnel.Client.Interface;
using LumaTunnel.Client.Profiles;
using LumaTunnel.Shared.Windows;
using XFEExtension.NetCore.WinUIHelper.Implements.Services;
namespace LumaTunnel.Client.Implements;
public sealed class SystemProxyService(ClientProfileStore profileStore) : GlobalServiceBase, ISystemProxyService, IDisposable
{
private readonly string _snapshotPath = Path.Combine(profileStore.StatePath, "system-proxy-snapshot.json");
private NamedPipeServerStream? _watchdogPipe;
private Process? _watchdog;
private SystemProxySnapshot? _snapshot;
private string? _ownerId;
private int _httpPort;
public bool IsEnabled { get; private set; }
public async Task EnableAsync(int httpPort, CancellationToken cancellationToken = default)
{
if (IsEnabled)
return;
_ownerId = Guid.NewGuid().ToString("N");
_httpPort = httpPort;
_snapshot = SystemProxyRegistry.Capture(_ownerId);
await SystemProxyRegistry.SaveSnapshotAsync(_snapshotPath, _snapshot, cancellationToken).ConfigureAwait(false);
SystemProxyRegistry.ApplyLoopbackProxy(httpPort);
IsEnabled = true;
StartWatchdog();
}
public async Task<bool> DisableAsync(CancellationToken cancellationToken = default)
{
if (!IsEnabled && _snapshot is null)
return false;
var snapshot = _snapshot ?? await SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath, cancellationToken).ConfigureAwait(false);
var restored = snapshot is not null && SystemProxyRegistry.RestoreIfOwned(snapshot, _httpPort);
IsEnabled = false;
await SignalNormalExitAsync(cancellationToken).ConfigureAwait(false);
_snapshot = null;
if (File.Exists(_snapshotPath)) File.Delete(_snapshotPath);
return restored;
}
public bool EmergencyRestore()
{
try
{
var snapshot = _snapshot ?? SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath).GetAwaiter().GetResult();
return snapshot is not null && SystemProxyRegistry.RestoreIfOwned(snapshot, _httpPort == 0 ? AppServices.Profile.HttpPort : _httpPort);
}
catch
{
return false;
}
}
public async Task<bool> RecoverStaleSnapshotAsync(int expectedHttpPort, CancellationToken cancellationToken = default)
{
var snapshot = await SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath, cancellationToken).ConfigureAwait(false);
if (snapshot is null) return false;
var restored = SystemProxyRegistry.RestoreIfOwned(snapshot, expectedHttpPort);
if (restored && File.Exists(_snapshotPath)) File.Delete(_snapshotPath);
return restored;
}
private void StartWatchdog()
{
var executable = Path.Combine(AppContext.BaseDirectory, "LumaTunnel.Watchdog.exe");
if (!File.Exists(executable) || _ownerId is null)
return;
var pipeName = "LumaTunnel.Watchdog." + _ownerId;
_watchdogPipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
_ = _watchdogPipe.WaitForConnectionAsync();
var startInfo = new ProcessStartInfo(executable) { UseShellExecute = false, CreateNoWindow = true };
foreach (var argument in new[] { "--parent", Environment.ProcessId.ToString(System.Globalization.CultureInfo.InvariantCulture), "--pipe", pipeName, "--snapshot", _snapshotPath, "--owner", _ownerId, "--port", _httpPort.ToString(System.Globalization.CultureInfo.InvariantCulture) })
startInfo.ArgumentList.Add(argument);
_watchdog = Process.Start(startInfo);
}
private async Task SignalNormalExitAsync(CancellationToken cancellationToken)
{
try
{
if (_watchdogPipe is { IsConnected: true } pipe)
{
await pipe.WriteAsync("normal"u8.ToArray(), cancellationToken).ConfigureAwait(false);
await pipe.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (IOException)
{
}
finally
{
_watchdogPipe?.Dispose();
_watchdogPipe = null;
_watchdog?.Dispose();
_watchdog = null;
}
}
public void Dispose()
{
_watchdogPipe?.Dispose();
_watchdog?.Dispose();
GC.SuppressFinalize(this);
}
}
using System.Diagnostics;
using System.IO.Pipes;
using LumaTunnel.Client.Interface;
using LumaTunnel.Client.Profiles;
using LumaTunnel.Shared.Windows;
using XFEExtension.NetCore.WinUIHelper.Implements.Services;
namespace LumaTunnel.Client.Implements;
public sealed class SystemProxyService(ClientProfileStore profileStore) : GlobalServiceBase, ISystemProxyService, IDisposable
{
private readonly string _snapshotPath = Path.Combine(profileStore.StatePath, "system-proxy-snapshot.json");
private NamedPipeServerStream? _watchdogPipe;
private Process? _watchdog;
private SystemProxySnapshot? _snapshot;
private string? _ownerId;
private int _httpPort;
public bool IsEnabled { get; private set; }
public async Task EnableAsync(int httpPort, CancellationToken cancellationToken = default)
{
if (IsEnabled)
return;
_ownerId = Guid.NewGuid().ToString("N");
_httpPort = httpPort;
_snapshot = SystemProxyRegistry.Capture(_ownerId);
await SystemProxyRegistry.SaveSnapshotAsync(_snapshotPath, _snapshot, cancellationToken).ConfigureAwait(false);
SystemProxyRegistry.ApplyLoopbackProxy(httpPort);
IsEnabled = true;
StartWatchdog();
}
public async Task<bool> DisableAsync(CancellationToken cancellationToken = default)
{
if (!IsEnabled && _snapshot is null)
return false;
var snapshot = _snapshot ?? await SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath, cancellationToken).ConfigureAwait(false);
var restored = snapshot is not null && SystemProxyRegistry.RestoreIfOwned(snapshot, _httpPort);
IsEnabled = false;
await SignalNormalExitAsync(cancellationToken).ConfigureAwait(false);
_snapshot = null;
if (File.Exists(_snapshotPath)) File.Delete(_snapshotPath);
return restored;
}
public bool EmergencyRestore()
{
try
{
var snapshot = _snapshot ?? SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath).GetAwaiter().GetResult();
return snapshot is not null && SystemProxyRegistry.RestoreIfOwned(snapshot, _httpPort == 0 ? AppServices.Profile.HttpPort : _httpPort);
}
catch
{
return false;
}
}
public async Task<bool> RecoverStaleSnapshotAsync(int expectedHttpPort, CancellationToken cancellationToken = default)
{
var snapshot = await SystemProxyRegistry.LoadSnapshotAsync(_snapshotPath, cancellationToken).ConfigureAwait(false);
if (snapshot is null) return false;
var restored = SystemProxyRegistry.RestoreIfOwned(snapshot, expectedHttpPort);
if (restored && File.Exists(_snapshotPath)) File.Delete(_snapshotPath);
return restored;
}
private void StartWatchdog()
{
var executable = Path.Combine(AppContext.BaseDirectory, "LumaTunnel.Watchdog.exe");
if (!File.Exists(executable) || _ownerId is null)
return;
var pipeName = "LumaTunnel.Watchdog." + _ownerId;
_watchdogPipe = new NamedPipeServerStream(pipeName, PipeDirection.Out, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
_ = _watchdogPipe.WaitForConnectionAsync();
var startInfo = new ProcessStartInfo(executable) { UseShellExecute = false, CreateNoWindow = true };
foreach (var argument in new[] { "--parent", Environment.ProcessId.ToString(System.Globalization.CultureInfo.InvariantCulture), "--pipe", pipeName, "--snapshot", _snapshotPath, "--owner", _ownerId, "--port", _httpPort.ToString(System.Globalization.CultureInfo.InvariantCulture) })
startInfo.ArgumentList.Add(argument);
_watchdog = Process.Start(startInfo);
}
private async Task SignalNormalExitAsync(CancellationToken cancellationToken)
{
try
{
if (_watchdogPipe is { IsConnected: true } pipe)
{
await pipe.WriteAsync("normal"u8.ToArray(), cancellationToken).ConfigureAwait(false);
await pipe.FlushAsync(cancellationToken).ConfigureAwait(false);
}
}
catch (IOException)
{
}
finally
{
_watchdogPipe?.Dispose();
_watchdogPipe = null;
_watchdog?.Dispose();
_watchdog = null;
}
}
public void Dispose()
{
_watchdogPipe?.Dispose();
_watchdog?.Dispose();
GC.SuppressFinalize(this);
}
}