Skip to content

Commit

Permalink
Add various improvements and minor fixes on plain text websocket (#274)
Browse files Browse the repository at this point in the history
* Add a dedicated state holder for processing exchange

Instead of relying on the CONNECT method

* Add faster and more reliable way to detect if incoming connection is TLS

* Handle websocket with plain connection

* Activate more unit tests
  • Loading branch information
haga-rak authored May 29, 2024
1 parent 18fadd3 commit b8acd3c
Show file tree
Hide file tree
Showing 6 changed files with 57 additions and 41 deletions.
9 changes: 8 additions & 1 deletion src/Fluxzy.Core/Core/Exchange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,11 @@ public Exchange(
/// </summary>
internal Task<bool> Complete => ExchangeCompletionSource.Task;

/// <summary>
///
/// </summary>
internal bool Unprocessed { get; set; }

/// <summary>
/// The remote authority
/// </summary>
Expand Down Expand Up @@ -200,7 +205,9 @@ public static Exchange CreateUntrackedExchange(
{
return new Exchange(idProvider, context, authority,
requestHeaderPlain, requestBody, responseHeader, responseBody, isSecure, httpVersion,
receivedFromProxy);
receivedFromProxy) {
Unprocessed = true
};
}

/// <summary>
Expand Down
29 changes: 17 additions & 12 deletions src/Fluxzy.Core/Core/FromProxyConnectSourceProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Fluxzy.Clients.H11;
using Fluxzy.Misc.ResizableBuffers;
using Fluxzy.Misc.Streams;
using Fluxzy.Utils;

namespace Fluxzy.Core
{
Expand Down Expand Up @@ -77,7 +78,8 @@ private async Task<InternalInitClientConnectionResult> InternalInitClientConnect

// Classic TLS Request

if (plainHeader.Method.Span.Equals("CONNECT", StringComparison.OrdinalIgnoreCase))
if (plainHeader.Method.Span.Equals("CONNECT", StringComparison.OrdinalIgnoreCase)
&& !UrlHelper.IsAbsoluteHttpUrl(plainHeader.Path.Span))
{
// GET Authority
var authorityArray =
Expand All @@ -92,22 +94,24 @@ private async Task<InternalInitClientConnectionResult> InternalInitClientConnect

return new(true, null);
}



await stream.WriteAsync(new ReadOnlyMemory<byte>(AcceptTunnelResponse), token);
var exchangeContext = await contextBuilder.Create(authority, true).ConfigureAwait(false);

if (exchangeContext.BlindMode) {

var provisionalExchange = Exchange.CreateUntrackedExchange(_idProvider, exchangeContext,
authority, plainHeaderChars, Stream.Null,
ProxyConstants.AcceptTunnelResponseString.AsMemory(),
Stream.Null, false,
"HTTP/1.1",
receivedFromProxy);

provisionalExchange.Unprocessed = false;

return
new (false, new ExchangeSourceInitResult(
authority, stream, stream,
Exchange.CreateUntrackedExchange(_idProvider, exchangeContext,
authority, plainHeaderChars, Stream.Null,
ProxyConstants.AcceptTunnelResponseString.AsMemory(),
Stream.Null, false,
"HTTP/1.1",
receivedFromProxy), true));
authority, stream, stream, provisionalExchange, true));
}

var certStart = ITimingProvider.Default.Instant();
Expand All @@ -116,10 +120,12 @@ private async Task<InternalInitClientConnectionResult> InternalInitClientConnect
var authenticateResult = await _secureConnectionUpdater.AuthenticateAsServer(
stream, authority.HostName, exchangeContext, token).ConfigureAwait(false);

authority = new Authority(authority.HostName, authority.Port, authenticateResult.IsSsl);

var exchange = Exchange.CreateUntrackedExchange(_idProvider, exchangeContext,
authority, plainHeaderChars, Stream.Null,
ProxyConstants.AcceptTunnelResponseString.AsMemory(),
Stream.Null, false, "HTTP/1.1", receivedFromProxy);
Stream.Null, authenticateResult.IsSsl, "HTTP/1.1", receivedFromProxy);

exchange.Metrics.CreateCertStart = certStart;
exchange.Metrics.CreateCertEnd = certEnd;
Expand All @@ -132,7 +138,6 @@ private async Task<InternalInitClientConnectionResult> InternalInitClientConnect
authenticateResult.OutStream, exchange, false));
}


var remainder = blockReadResult.TotalReadLength - blockReadResult.HeaderLength;

if (remainder > 0)
Expand Down
36 changes: 13 additions & 23 deletions src/Fluxzy.Core/Core/Impl/SecureConnectionUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
using System.IO;
using System.Net.Security;
using System.Net.Sockets;
using System.Runtime.CompilerServices;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Fluxzy.Misc.Streams;
Expand All @@ -23,18 +23,16 @@ public SecureConnectionUpdater(ICertificateProvider certificateProvider)
_certificateProvider = certificateProvider;
}

private static bool StartWithKeyWord(ReadOnlySpan<byte> buffer)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static bool DetectTlsClientHello(ReadOnlySpan<byte> data)
{
Span<char> bufferChar = stackalloc char[4];
Encoding.ASCII.GetChars(buffer, bufferChar);

return ((ReadOnlySpan<char>) bufferChar).Equals("GET ", StringComparison.OrdinalIgnoreCase);
return data[0] == 0x16;
}

public async Task<SecureConnectionUpdateResult> AuthenticateAsServer(
Stream stream, string host, ExchangeContext context, CancellationToken token)
{
var buffer = new byte[4];
var buffer = new byte[1];
var originalStream = stream;

if (stream is NetworkStream networkStream && networkStream.DataAvailable) {
Expand All @@ -44,11 +42,10 @@ public async Task<SecureConnectionUpdateResult> AuthenticateAsServer(
await stream.ReadExactAsync(buffer, token).ConfigureAwait(false);
}

if (StartWithKeyWord(buffer)) {
// This is websocket request

return new SecureConnectionUpdateResult(false, true,
new CombinedReadonlyStream(false, new MemoryStream(buffer), stream),
if (!DetectTlsClientHello(buffer)) {
// This is a regular CONNECT request without SSL

return new SecureConnectionUpdateResult(false, new CombinedReadonlyStream(false, new MemoryStream(buffer), stream),
stream);
}

Expand Down Expand Up @@ -81,22 +78,15 @@ await secureStream
};
}

return new SecureConnectionUpdateResult(false, true,
secureStream,
secureStream);
return new SecureConnectionUpdateResult(true, secureStream, secureStream);
}
}

public record SecureConnectionUpdateResult(
bool IsSsl, bool IsWebSocket,
Stream InStream, Stream OutStream)
internal record SecureConnectionUpdateResult(
bool IsSsl, Stream InStream, Stream OutStream)
{
public bool IsSsl { get; } = IsSsl;

public bool IsWebSocket { get; } = IsWebSocket;

public bool IsOnError => !IsSsl && !IsWebSocket;


public Stream InStream { get; } = InStream;

public Stream OutStream { get; } = OutStream;
Expand Down
7 changes: 3 additions & 4 deletions src/Fluxzy.Core/Core/ProxyOrchestrator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,10 +103,9 @@ public async ValueTask Operate(TcpClient client, RsBuffer buffer, bool closeImme
var shouldClose = false;

do {
if (
!exchange.Request.Header.Method.Span.Equals("connect", StringComparison.OrdinalIgnoreCase)
|| exchangeSourceInitResult.TunnelOnly
) {
var processMessage = !exchange.Unprocessed;

if (processMessage) {
// Check whether the local browser ask for a connection close

if (D.EnableTracing) {
Expand Down
15 changes: 15 additions & 0 deletions src/Fluxzy.Core/Utils/UrlHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// Copyright 2021 - Haga Rakotoharivelo - https://github.com/haga-rak

using System;

namespace Fluxzy.Utils
{
internal static class UrlHelper
{
public static bool IsAbsoluteHttpUrl(ReadOnlySpan<char> rawUrl)
{
return rawUrl.StartsWith("http://".AsSpan(), StringComparison.OrdinalIgnoreCase) ||
rawUrl.StartsWith("https://".AsSpan(), StringComparison.OrdinalIgnoreCase);
}
}
}
2 changes: 1 addition & 1 deletion test/Fluxzy.Tests/Cli/CliWebSockets.cs
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ await ws.SendAsync(messageBytes, WebSocketMessageType.Text,
Directory.Delete(directoryName, true);
}

[Theory(Skip = "Websocket test server is not stable on sandbox.smartizy.com")]
[Theory()]
[MemberData(nameof(GetCliWebSocketTestRepetitionArgs))]
public async Task Run_Cli_For_Web_Socket_Req_Res(int length, int _)
{
Expand Down

0 comments on commit b8acd3c

Please sign in to comment.