LumaTunnel
【WinUI】LumaTunnel 是一个面向个人多设备的 Windows 代理系统。客户端在本机提供 HTTP/HTTPS CONNECT 与 SOCKS5 TCP 代理,并通过一个受信任 TLS 证书保护的 WSS 会话,将多个 TCP 流复用到自建 Windows Server 节点。
关注
0
Fork
0
Star
0
using System.Threading.Channels;
using LumaTunnel.Client.Core.Abstractions;
using LumaTunnel.Shared.Protocol;
namespace LumaTunnel.Client.Core.Tunnel;
internal sealed class TunnelClientStream(TunnelService owner, ClientTunnelState state) : Stream, IHalfCloseable
{
private readonly TunnelService _owner = owner;
private readonly ClientTunnelState _state = state;
private byte[]? _current;
private int _currentOffset;
private int _disposed;
private int _writeClosed;
public override bool CanRead => Volatile.Read(ref _disposed) == 0;
public override bool CanSeek => false;
public override bool CanWrite => Volatile.Read(ref _disposed) == 0 && Volatile.Read(ref _writeClosed) == 0;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
if (buffer.IsEmpty)
return 0;
while (_current is null || _currentOffset == _current.Length)
{
_current = null;
_currentOffset = 0;
try
{
if (!await _state.Incoming.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
return 0;
if (!_state.Incoming.Reader.TryRead(out _current))
continue;
}
catch (ChannelClosedException exception) when (exception.InnerException is not null)
{
throw new IOException("The tunnel stream failed.", exception.InnerException);
}
}
var count = Math.Min(buffer.Length, _current.Length - _currentOffset);
_current.AsMemory(_currentOffset, count).CopyTo(buffer);
_currentOffset += count;
return count;
}
public override void Write(byte[] buffer, int offset, int count) => WriteAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
if (Volatile.Read(ref _writeClosed) != 0)
throw new IOException("The tunnel stream write side is closed.");
while (!buffer.IsEmpty)
{
var count = Math.Min(buffer.Length, TunnelProtocol.MaxPayloadLength);
await _owner.SendDataAsync(_state.StreamId, buffer[..count], cancellationToken).ConfigureAwait(false);
buffer = buffer[count..];
}
}
public async ValueTask HalfCloseWriteAsync(CancellationToken cancellationToken = default)
{
if (Interlocked.Exchange(ref _writeClosed, 1) == 0 && Volatile.Read(ref _disposed) == 0)
await _owner.SendHalfCloseAsync(_state.StreamId, cancellationToken).ConfigureAwait(false);
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
if (disposing && Interlocked.Exchange(ref _disposed, 1) == 0)
_owner.CloseStreamAsync(_state.StreamId).AsTask().GetAwaiter().GetResult();
base.Dispose(disposing);
}
public override async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
await _owner.CloseStreamAsync(_state.StreamId).ConfigureAwait(false);
await base.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
}
using System.Threading.Channels;
using LumaTunnel.Client.Core.Abstractions;
using LumaTunnel.Shared.Protocol;
namespace LumaTunnel.Client.Core.Tunnel;
internal sealed class TunnelClientStream(TunnelService owner, ClientTunnelState state) : Stream, IHalfCloseable
{
private readonly TunnelService _owner = owner;
private readonly ClientTunnelState _state = state;
private byte[]? _current;
private int _currentOffset;
private int _disposed;
private int _writeClosed;
public override bool CanRead => Volatile.Read(ref _disposed) == 0;
public override bool CanSeek => false;
public override bool CanWrite => Volatile.Read(ref _disposed) == 0 && Volatile.Read(ref _writeClosed) == 0;
public override long Length => throw new NotSupportedException();
public override long Position { get => throw new NotSupportedException(); set => throw new NotSupportedException(); }
public override void Flush() { }
public override Task FlushAsync(CancellationToken cancellationToken) => Task.CompletedTask;
public override int Read(byte[] buffer, int offset, int count) => ReadAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override async ValueTask<int> ReadAsync(Memory<byte> buffer, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
if (buffer.IsEmpty)
return 0;
while (_current is null || _currentOffset == _current.Length)
{
_current = null;
_currentOffset = 0;
try
{
if (!await _state.Incoming.Reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false))
return 0;
if (!_state.Incoming.Reader.TryRead(out _current))
continue;
}
catch (ChannelClosedException exception) when (exception.InnerException is not null)
{
throw new IOException("The tunnel stream failed.", exception.InnerException);
}
}
var count = Math.Min(buffer.Length, _current.Length - _currentOffset);
_current.AsMemory(_currentOffset, count).CopyTo(buffer);
_currentOffset += count;
return count;
}
public override void Write(byte[] buffer, int offset, int count) => WriteAsync(buffer.AsMemory(offset, count)).AsTask().GetAwaiter().GetResult();
public override async ValueTask WriteAsync(ReadOnlyMemory<byte> buffer, CancellationToken cancellationToken = default)
{
ObjectDisposedException.ThrowIf(Volatile.Read(ref _disposed) != 0, this);
if (Volatile.Read(ref _writeClosed) != 0)
throw new IOException("The tunnel stream write side is closed.");
while (!buffer.IsEmpty)
{
var count = Math.Min(buffer.Length, TunnelProtocol.MaxPayloadLength);
await _owner.SendDataAsync(_state.StreamId, buffer[..count], cancellationToken).ConfigureAwait(false);
buffer = buffer[count..];
}
}
public async ValueTask HalfCloseWriteAsync(CancellationToken cancellationToken = default)
{
if (Interlocked.Exchange(ref _writeClosed, 1) == 0 && Volatile.Read(ref _disposed) == 0)
await _owner.SendHalfCloseAsync(_state.StreamId, cancellationToken).ConfigureAwait(false);
}
public override long Seek(long offset, SeekOrigin origin) => throw new NotSupportedException();
public override void SetLength(long value) => throw new NotSupportedException();
protected override void Dispose(bool disposing)
{
if (disposing && Interlocked.Exchange(ref _disposed, 1) == 0)
_owner.CloseStreamAsync(_state.StreamId).AsTask().GetAwaiter().GetResult();
base.Dispose(disposing);
}
public override async ValueTask DisposeAsync()
{
if (Interlocked.Exchange(ref _disposed, 1) == 0)
await _owner.CloseStreamAsync(_state.StreamId).ConfigureAwait(false);
await base.DisposeAsync().ConfigureAwait(false);
GC.SuppressFinalize(this);
}
}