diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyCommand.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyCommand.cs new file mode 100644 index 000000000..9e94fc669 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyCommand.cs @@ -0,0 +1,26 @@ +using System; +using System.Buffers; +using System.Net; + +namespace SuperSocket.ProtoBase.ProxyProtocol +{ + public enum ProxyCommand + { + /// + /// 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. + /// + LOCAL, + + /// + /// 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. + /// + PROXY + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyInfo.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyInfo.cs new file mode 100644 index 000000000..0fc4e4d84 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyInfo.cs @@ -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() + { + } + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPackagePartReader.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPackagePartReader.cs new file mode 100644 index 000000000..b0b2840a4 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPackagePartReader.cs @@ -0,0 +1,22 @@ +using System; +using System.Buffers; + +namespace SuperSocket.ProtoBase.ProxyProtocol +{ + abstract class ProxyProtocolPackagePartReader : IPackagePartReader + { + static ProxyProtocolPackagePartReader() + { + ProxyProtocolSwitch = new ProxyProtocolSwitchPartReader(); + ProxyProtocolV1Reader = new ProxyProtocolV1PartReader(); + } + + public abstract bool Process(TPackageInfo package, object filterContext, SequenceReader reader, out IPackagePartReader nextPartReader, out bool needMoreData); + + internal static IPackagePartReader ProxyProtocolSwitch { get; } + + internal static IPackagePartReader ProxyProtocolV1Reader { get; } + + internal static IPackagePartReader ProxyProtocolV2Reader { get; } + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPipelineFilter.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPipelineFilter.cs new file mode 100644 index 000000000..b885867c9 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolPipelineFilter.cs @@ -0,0 +1,49 @@ +using System; +using System.Buffers; + +namespace SuperSocket.ProtoBase.ProxyProtocol +{ + public class ProxyProtocolPipelineFilter : PackagePartsPipelineFilter + where TPackageInfo : class + { + private readonly IPipelineFilter _applicationPipelineFilter; + + private object _originalFilterContext; + + public ProxyInfo ProxyInfo { get; private set; } + + public ProxyProtocolPipelineFilter(IPipelineFilter applicationPipelineFilter) + { + _applicationPipelineFilter = applicationPipelineFilter; + } + + protected override TPackageInfo CreatePackage() + { + return default; + } + + protected override IPackagePartReader GetFirstPartReader() + { + return ProxyProtocolPackagePartReader.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 reader) + { + if (ProxyInfo == null) + { + _originalFilterContext = Context; + Context = ProxyInfo = new ProxyInfo(); + } + + return base.Filter(reader); + } + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolSwitchPartReader.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolSwitchPartReader.cs new file mode 100644 index 000000000..4bfa5d4df --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolSwitchPartReader.cs @@ -0,0 +1,85 @@ +using System; +using System.Buffers; +using System.Text; + +namespace SuperSocket.ProtoBase.ProxyProtocol +{ + class ProxyProtocolSwitchPartReader : ProxyProtocolPackagePartReader + { + 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 reader, out IPackagePartReader 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 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 == ' '; + } + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV1PartReader.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV1PartReader.cs new file mode 100644 index 000000000..08be63651 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV1PartReader.cs @@ -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 : ProxyProtocolPackagePartReader + { + 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 reader, out IPackagePartReader nextPartReader, out bool needMoreData) + { + if (!reader.TryReadTo(out ReadOnlySequence proxyLineSequence, PROXY_DELIMITER, true)) + { + needMoreData = true; + nextPartReader = null; + return false; + } + + needMoreData = false; + nextPartReader = null; + + var proxyLineReader = new SequenceReader(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 segment, ProxyInfo proxyInfo); + } + + class SourceIPAddressProcessor : IProxySgementProcessor + { + public void Process(ReadOnlySpan segment, ProxyInfo proxyInfo) + { + proxyInfo.SourceIPAddress = IPAddress.Parse(segment); + } + } + + class DestinationIPAddressProcessor : IProxySgementProcessor + { + public void Process(ReadOnlySpan segment, ProxyInfo proxyInfo) + { + proxyInfo.DestinationIPAddress = IPAddress.Parse(segment); + } + } + + class SourcePortProcessor : IProxySgementProcessor + { + public void Process(ReadOnlySpan segment, ProxyInfo proxyInfo) + { + proxyInfo.SourcePort = int.Parse(segment); + } + } + + class DestinationPortProcessor : IProxySgementProcessor + { + public void Process(ReadOnlySpan segment, ProxyInfo proxyInfo) + { + proxyInfo.SourcePort = int.Parse(segment); + } + } + } +} \ No newline at end of file diff --git a/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV2PartReader.cs b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV2PartReader.cs new file mode 100644 index 000000000..2e1139e40 --- /dev/null +++ b/src/SuperSocket.ProtoBase/ProxyProtocol/ProxyProtocolV2PartReader.cs @@ -0,0 +1,121 @@ +using System; +using System.Buffers; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Net.Http; +using System.Net.Sockets; +using System.Text; +using Microsoft.VisualBasic; + +namespace SuperSocket.ProtoBase.ProxyProtocol +{ + class ProxyProtocolV2PartReader : ProxyProtocolPackagePartReader + { + private static readonly int FIXPART_LEN_AFTER_SIGNATURE = 4; + + private static readonly ArrayPool _bufferPool = ArrayPool.Shared; + + public override bool Process(TPackageInfo package, object filterContext, SequenceReader reader, out IPackagePartReader nextPartReader, out bool needMoreData) + { + nextPartReader = null; + + var proxyInfo = filterContext as ProxyInfo; + + if (proxyInfo.AddressLength == 0) + { + if (reader.Length < FIXPART_LEN_AFTER_SIGNATURE) + { + needMoreData = true; + return false; + } + + reader.TryRead(out var versionAndCommand); + + proxyInfo.Version = (int)versionAndCommand / 16; + proxyInfo.Command = (versionAndCommand % 16) == 0 ? ProxyCommand.LOCAL : ProxyCommand.PROXY; + + reader.TryRead(out var addressFamilyAndProtocol); + + proxyInfo.AddressFamily = ((int)addressFamilyAndProtocol / 16) switch + { + 0 => AddressFamily.Unspecified, + 1 => AddressFamily.InterNetwork, + 2 => AddressFamily.InterNetworkV6, + 3 => AddressFamily.Unix, + _ => throw new NotSupportedException(), + }; + + proxyInfo.ProtocolType = ((int)addressFamilyAndProtocol % 16) switch + { + 0 => ProtocolType.Unspecified, + 1 => ProtocolType.Tcp, + 2 => ProtocolType.Udp, + _ => throw new NotSupportedException(), + }; + + reader.TryRead(out var len_high); + reader.TryRead(out var len_low); + + proxyInfo.AddressLength = len_high * 256 + len_low; + + needMoreData = false; + return false; + } + + if (reader.Length < proxyInfo.AddressLength) + { + needMoreData = true; + return false; + } + + needMoreData = false; + + if (proxyInfo.AddressFamily == AddressFamily.InterNetwork) + { + reader.TryReadBigEndian(out UInt32 sourceIpData); + proxyInfo.SourceIPAddress = new IPAddress(sourceIpData); + + reader.TryReadBigEndian(out UInt32 destinationIpData); + proxyInfo.DestinationIPAddress = new IPAddress(destinationIpData); + + reader.TryReadBigEndian(out UInt16 sourcePort); + proxyInfo.SourcePort = sourcePort; + + reader.TryReadBigEndian(out UInt16 destinationPort); + proxyInfo.DestinationPort = destinationPort; + } + else if (proxyInfo.AddressFamily == AddressFamily.InterNetworkV6) + { + var addressBuffer = _bufferPool.Rent(32); + + try + { + var addressBufferSpan = addressBuffer.AsSpan()[..32]; + + reader.Sequence.Slice(0, 32).CopyTo(addressBufferSpan); + reader.Advance(32); + + proxyInfo.SourceIPAddress = new IPAddress(addressBufferSpan); + + reader.Sequence.Slice(0, 32).CopyTo(addressBufferSpan); + reader.Advance(32); + + proxyInfo.DestinationIPAddress = new IPAddress(addressBufferSpan); + } + finally + { + _bufferPool.Return(addressBuffer); + } + + reader.TryReadBigEndian(out UInt16 sourcePort); + proxyInfo.SourcePort = sourcePort; + + reader.TryReadBigEndian(out UInt16 destinationPort); + proxyInfo.DestinationPort = destinationPort; + } + + return true; + } + } +} \ No newline at end of file