Skip to content

Commit

Permalink
working on ProxyProtocol
Browse files Browse the repository at this point in the history
  • Loading branch information
kerryjiang committed Aug 10, 2024
1 parent 217978b commit 08e600c
Show file tree
Hide file tree
Showing 7 changed files with 437 additions and 0 deletions.
26 changes: 26 additions & 0 deletions src/SuperSocket.ProtoBase/ProxyProtocol/ProxyCommand.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Buffers;
using System.Net;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
public enum ProxyCommand
{
/// <summary>
/// the connection was established on purpose by the proxy
/// without being relayed. The connection endpoints are the sender and the
/// receiver. Such connections exist when the proxy sends health-checks to the
/// server. The receiver must accept this connection as valid and must use the
/// real connection endpoints and discard the protocol block including the
/// family which is ignored.
/// </summary>
LOCAL,

/// <summary>
/// the connection was established on behalf of another node,
/// and reflects the original connection endpoints. The receiver must then use
/// the information provided in the protocol block to get original the address.
/// </summary>
PROXY
}
}
32 changes: 32 additions & 0 deletions src/SuperSocket.ProtoBase/ProxyProtocol/ProxyInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using System;
using System.Buffers;
using System.Net;
using System.Net.Sockets;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
public class ProxyInfo
{
public IPAddress SourceIPAddress { get; internal set; }

public int SourcePort { get; internal set; }

public IPAddress DestinationIPAddress { get; internal set; }

public int DestinationPort { get; internal set; }

public ProxyCommand Command { get; internal set; }

public int Version { get; internal set; }

public AddressFamily AddressFamily { get; internal set; }

public ProtocolType ProtocolType { get; internal set; }

internal int AddressLength { get; set; }

public ProxyInfo()
{
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;
using System.Buffers;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
abstract class ProxyProtocolPackagePartReader<TPackageInfo> : IPackagePartReader<TPackageInfo>
{
static ProxyProtocolPackagePartReader()
{
ProxyProtocolSwitch = new ProxyProtocolSwitchPartReader<TPackageInfo>();
ProxyProtocolV1Reader = new ProxyProtocolV1PartReader<TPackageInfo>();
}

public abstract bool Process(TPackageInfo package, object filterContext, SequenceReader<byte> reader, out IPackagePartReader<TPackageInfo> nextPartReader, out bool needMoreData);

internal static IPackagePartReader<TPackageInfo> ProxyProtocolSwitch { get; }

internal static IPackagePartReader<TPackageInfo> ProxyProtocolV1Reader { get; }

internal static IPackagePartReader<TPackageInfo> ProxyProtocolV2Reader { get; }
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System;
using System.Buffers;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
public class ProxyProtocolPipelineFilter<TPackageInfo> : PackagePartsPipelineFilter<TPackageInfo>
where TPackageInfo : class
{
private readonly IPipelineFilter<TPackageInfo> _applicationPipelineFilter;

private object _originalFilterContext;

public ProxyInfo ProxyInfo { get; private set; }

public ProxyProtocolPipelineFilter(IPipelineFilter<TPackageInfo> applicationPipelineFilter)
{
_applicationPipelineFilter = applicationPipelineFilter;
}

protected override TPackageInfo CreatePackage()
{
return default;
}

protected override IPackagePartReader<TPackageInfo> GetFirstPartReader()
{
return ProxyProtocolPackagePartReader<TPackageInfo>.ProxyProtocolSwitch;
}

public override void Reset()
{
// This method will be called when the proxy package handling finishes
NextFilter = _applicationPipelineFilter;
base.Reset();
Context = _originalFilterContext;
}

public override TPackageInfo Filter(SequenceReader<byte> reader)
{
if (ProxyInfo == null)
{
_originalFilterContext = Context;
Context = ProxyInfo = new ProxyInfo();
}

return base.Filter(reader);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
using System;
using System.Buffers;
using System.Text;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
class ProxyProtocolSwitchPartReader<TPackageInfo> : ProxyProtocolPackagePartReader<TPackageInfo>
{
private const int SWITCH_PART_SIZE = 12;

private static readonly byte[] PROXYPROTOCOL_V2_SIGNATURE = new byte[] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A };

private static readonly byte[] PROXY_TAG = Encoding.ASCII.GetBytes("PROXY TCP");

public override bool Process(TPackageInfo package, object filterContext, SequenceReader<byte> reader, out IPackagePartReader<TPackageInfo> nextPartReader, out bool needMoreData)
{
nextPartReader = null;

if (reader.Length < SWITCH_PART_SIZE)
{
needMoreData = true;
return false;
}

needMoreData = false;

if (reader.IsNext(PROXYPROTOCOL_V2_SIGNATURE, true))
{
nextPartReader = ProxyProtocolV2Reader;
return false;
}

int read = 0;

try
{
if (TryReadProxyProtocolV1Tag(reader, out read))
{
nextPartReader = ProxyProtocolV1Reader;
return false;
}

return true;
}
finally
{
if (read > 0)
reader.Rewind(read);
}
}

private bool TryReadProxyProtocolV1Tag(SequenceReader<byte> reader, out int read)
{
read = 0;

if (!reader.IsNext(PROXY_TAG, true))
{
return false;
}

read += PROXY_TAG.Length;

if (!reader.TryRead(out var ver))
{
return false;
}

read++;

if (ver != '4' && ver != '6')
{
return false;
}

if (!reader.TryRead(out var space))
{
return false;
}

read++;

return space == ' ';
}
}
}
102 changes: 102 additions & 0 deletions src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV1PartReader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
using System;
using System.Buffers;
using System.Collections;
using System.Collections.Generic;
using System.Net;
using System.Text;
using Microsoft.VisualBasic;

namespace SuperSocket.ProtoBase.ProxyProtocol
{
class ProxyProtocolV1PartReader<TPackageInfo> : ProxyProtocolPackagePartReader<TPackageInfo>
{
private static readonly byte[] PROXY_DELIMITER = Encoding.ASCII.GetBytes("\r\n");

private static readonly IProxySgementProcessor[] PROXY_SEGMENT_PARSERS = new IProxySgementProcessor[]
{
new SourceIPAddressProcessor(),
new DestinationIPAddressProcessor(),
new SourcePortProcessor(),
new DestinationPortProcessor()
};

public override bool Process(TPackageInfo package, object filterContext, SequenceReader<byte> reader, out IPackagePartReader<TPackageInfo> nextPartReader, out bool needMoreData)
{
if (!reader.TryReadTo(out ReadOnlySequence<byte> proxyLineSequence, PROXY_DELIMITER, true))
{
needMoreData = true;
nextPartReader = null;
return false;
}

needMoreData = false;
nextPartReader = null;

var proxyLineReader = new SequenceReader<byte>(proxyLineSequence);

var proxyLine = proxyLineReader.ReadString();

LoadProxyInfo(filterContext as ProxyInfo, proxyLine, 12, 13);

return true;
}

private void LoadProxyInfo(ProxyInfo proxyInfo, string line, int startPos, int lookForOffet)
{
var span = line.AsSpan();
var segmentIndex = 0;

while (lookForOffet < line.Length)
{
var spacePos = line.IndexOf(' ', lookForOffet);

if (spacePos < 0)
break;

startPos = spacePos + 1;
lookForOffet = startPos + 1;

var segment = span.Slice(startPos, spacePos - startPos);

PROXY_SEGMENT_PARSERS[segmentIndex++].Process(segment, proxyInfo);
}
}

interface IProxySgementProcessor
{
void Process(ReadOnlySpan<char> segment, ProxyInfo proxyInfo);
}

class SourceIPAddressProcessor : IProxySgementProcessor
{
public void Process(ReadOnlySpan<char> segment, ProxyInfo proxyInfo)
{
proxyInfo.SourceIPAddress = IPAddress.Parse(segment);
}
}

class DestinationIPAddressProcessor : IProxySgementProcessor
{
public void Process(ReadOnlySpan<char> segment, ProxyInfo proxyInfo)
{
proxyInfo.DestinationIPAddress = IPAddress.Parse(segment);
}
}

class SourcePortProcessor : IProxySgementProcessor
{
public void Process(ReadOnlySpan<char> segment, ProxyInfo proxyInfo)
{
proxyInfo.SourcePort = int.Parse(segment);
}
}

class DestinationPortProcessor : IProxySgementProcessor
{
public void Process(ReadOnlySpan<char> segment, ProxyInfo proxyInfo)
{
proxyInfo.SourcePort = int.Parse(segment);
}
}
}
}
Loading

0 comments on commit 08e600c

Please sign in to comment.