(), rawContent);
+ // If we're not dealing with a SSL Type, we can use the same mechanism
+ case HAProxyTLV.Type.PP2_TYPE_ALPN:
+ case HAProxyTLV.Type.PP2_TYPE_AUTHORITY:
+ case HAProxyTLV.Type.PP2_TYPE_SSL_VERSION:
+ case HAProxyTLV.Type.PP2_TYPE_SSL_CN:
+ case HAProxyTLV.Type.PP2_TYPE_NETNS:
+ case HAProxyTLV.Type.OTHER:
+ return new HAProxyTLV(type, typeAsByte, header.ReadRetainedSlice(length));
+ default:
+ return null;
+ }
+ }
+
+ /**
+ * Decodes a version 1, human-readable proxy protocol header.
+ *
+ * @param header a version 1 proxy protocol header
+ * @return {@link HAProxyMessage} instance
+ * @throws HAProxyProtocolException if any portion of the header is invalid
+ */
+ internal static HAProxyMessage DecodeHeader(string header)
+ {
+ if (header == null)
+ {
+ throw new HAProxyProtocolException("header");
+ }
+
+ string[] parts = header.Split(' ');
+ int numParts = parts.Length;
+
+ if (numParts < 2)
+ {
+ throw new HAProxyProtocolException(
+ "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)");
+ }
+
+ if (!"PROXY".Equals(parts[0]))
+ {
+ throw new HAProxyProtocolException("unknown identifier: " + parts[0]);
+ }
+
+ HAProxyProxiedProtocol protAndFam;
+ try
+ {
+ protAndFam = HAProxyProxiedProtocol.ValueOf(parts[1]);
+ }
+ catch (ArgumentOutOfRangeException e)
+ {
+ throw new HAProxyProtocolException(e);
+ }
+
+ if (protAndFam != HAProxyProxiedProtocol.TCP4 &&
+ protAndFam != HAProxyProxiedProtocol.TCP6 &&
+ protAndFam != HAProxyProxiedProtocol.UNKNOWN)
+ {
+ throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]);
+ }
+
+ if (protAndFam == HAProxyProxiedProtocol.UNKNOWN)
+ {
+ return V1_UNKNOWN_MSG;
+ }
+
+ if (numParts != 6)
+ {
+ throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)");
+ }
+
+ return new HAProxyMessage(
+ HAProxyProtocolVersion.V1, HAProxyCommand.PROXY,
+ protAndFam, parts[2], parts[3], parts[4], parts[5]);
+ }
+
+ /**
+ * Convert ip address bytes to string representation
+ *
+ * @param header buffer containing ip address bytes
+ * @param addressLen number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
+ * @return string representation of the ip address
+ */
+ private static string IpBytesToString(IByteBuffer header, int addressLen)
+ {
+ StringBuilder sb = new StringBuilder();
+ if (addressLen == 4)
+ {
+ sb.Append(header.ReadByte() & 0xff);
+ sb.Append('.');
+ sb.Append(header.ReadByte() & 0xff);
+ sb.Append('.');
+ sb.Append(header.ReadByte() & 0xff);
+ sb.Append('.');
+ sb.Append(header.ReadByte() & 0xff);
+ }
+ else
+ {
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ sb.Append(':');
+ sb.Append(header.ReadUnsignedShort().ToString("x"));
+ }
+ return sb.ToString();
+ }
+
+ /**
+ * Convert port to integer
+ *
+ * @param value the port
+ * @return port as an integer
+ * @throws HAProxyProtocolException if port is not a valid integer
+ */
+ private static int PortStringToInt(string value)
+ {
+ int port;
+ try
+ {
+ port = int.Parse(value);
+ }
+ catch (FormatException e)
+ {
+ throw new HAProxyProtocolException(e);
+ }
+
+ if (port <= 0 || port > 65535)
+ {
+ throw new HAProxyProtocolException("invalid port: " + value + " (expected: 1 ~ 65535)");
+ }
+
+ return port;
+ }
+
+ /**
+ * Validate an address (IPv4, IPv6, Unix Socket)
+ *
+ * @param address human-readable address
+ * @param addrFamily the {@link AddressFamily} to check the address against
+ * @throws HAProxyProtocolException if the address is invalid
+ */
+ private static void CheckAddress(string address, AddressFamily addrFamily)
+ {
+ if (addrFamily == null)
+ {
+ throw new ArgumentNullException(nameof(addrFamily));
+ }
+
+ if (addrFamily == AddressFamily.AF_UNIX)
+ {
+ return;
+ }
+ else if(addrFamily == AddressFamily.AF_UNSPEC)
+ {
+ if (address != null)
+ {
+ throw new HAProxyProtocolException("unable to validate an AF_UNSPEC address: " + address);
+ }
+ return;
+ }
+
+ if (address == null)
+ {
+ throw new ArgumentNullException(nameof(address));
+ }
+
+ if (addrFamily == AddressFamily.AF_IPv4)
+ {
+ if (!NetUtil.IsValidIpV4Address(address))
+ {
+ throw new HAProxyProtocolException("invalid IPv4 address: " + address);
+ }
+ }
+ else if (addrFamily == AddressFamily.AF_IPv6)
+ {
+ if (!NetUtil.IsValidIpV6Address(address))
+ {
+ throw new HAProxyProtocolException("invalid IPv6 address: " + address);
+ }
+ }
+ else
+ {
+ throw new HAProxyProtocolException("Not possible");
+ }
+ }
+
+ /**
+ * Validate a UDP/TCP port
+ *
+ * @param port the UDP/TCP port
+ * @throws HAProxyProtocolException if the port is out of range (0-65535 inclusive)
+ */
+ private static void CheckPort(int port)
+ {
+ if (port < 0 || port > 65535)
+ {
+ throw new HAProxyProtocolException("invalid port: " + port + " (expected: 1 ~ 65535)");
+ }
+ }
+
+ /**
+ * Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyMessage}.
+ */
+ public HAProxyProtocolVersion ProtocolVersion()
+ {
+ return this.protocolVersion;
+ }
+
+ /**
+ * Returns the {@link HAProxyCommand} of this {@link HAProxyMessage}.
+ */
+ public HAProxyCommand Command()
+ {
+ return this.command;
+ }
+
+ /**
+ * Returns the {@link HAProxyProxiedProtocol} of this {@link HAProxyMessage}.
+ */
+ public HAProxyProxiedProtocol ProxiedProtocol()
+ {
+ return this.proxiedProtocol;
+ }
+
+ /**
+ * Returns the human-readable source address of this {@link HAProxyMessage}.
+ */
+ public string SourceAddress()
+ {
+ return this.sourceAddress;
+ }
+
+ /**
+ * Returns the human-readable destination address of this {@link HAProxyMessage}.
+ */
+ public string DestinationAddress()
+ {
+ return this.destinationAddress;
+ }
+
+ /**
+ * Returns the UDP/TCP source port of this {@link HAProxyMessage}.
+ */
+ public int SourcePort()
+ {
+ return this.sourcePort;
+ }
+
+ /**
+ * Returns the UDP/TCP destination port of this {@link HAProxyMessage}.
+ */
+ public int DestinationPort()
+ {
+ return this.destinationPort;
+ }
+
+ /**
+ * Returns a list of {@link HAProxyTLV} or an empty list if no TLVs are present.
+ *
+ * TLVs are only available for the Proxy Protocol V2
+ */
+ public IList Tlvs()
+ {
+ return this.tlvs;
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxyMessageDecoder.cs b/src/DotNetty.Codecs.HaProxy/HAProxyMessageDecoder.cs
new file mode 100644
index 000000000..b80f92532
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxyMessageDecoder.cs
@@ -0,0 +1,452 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy
+{
+ using System;
+ using System.Collections.Generic;
+ using System.Text;
+ using DotNetty.Buffers;
+ using DotNetty.Transport.Channels;
+
+ /**
+ * Decodes an HAProxy proxy protocol header
+ *
+ * @see Proxy Protocol Specification
+ */
+ public class HAProxyMessageDecoder : ByteToMessageDecoder
+ {
+ /**
+ * Maximum possible length of a v1 proxy protocol header per spec
+ */
+ const int V1_MAX_LENGTH = 108;
+
+ /**
+ * Maximum possible length of a v2 proxy protocol header (fixed 16 bytes + max unsigned short)
+ */
+ const int V2_MAX_LENGTH = 16 + 65535;
+
+ /**
+ * Minimum possible length of a fully functioning v2 proxy protocol header (fixed 16 bytes + v2 address info space)
+ */
+ const int V2_MIN_LENGTH = 16 + 216;
+
+ /**
+ * Maximum possible length for v2 additional TLV data (max unsigned short - max v2 address info space)
+ */
+ const int V2_MAX_TLV = 65535 - 216;
+
+ /**
+ * Version 1 header delimiter is always '\r\n' per spec
+ */
+ const int DELIMITER_LENGTH = 2;
+
+ /**
+ * Binary header prefix
+ */
+ static readonly byte[] BINARY_PREFIX = {
+ (byte) 0x0D,
+ (byte) 0x0A,
+ (byte) 0x0D,
+ (byte) 0x0A,
+ (byte) 0x00,
+ (byte) 0x0D,
+ (byte) 0x0A,
+ (byte) 0x51,
+ (byte) 0x55,
+ (byte) 0x49,
+ (byte) 0x54,
+ (byte) 0x0A
+ };
+
+ static readonly byte[] TEXT_PREFIX = {
+ (byte) 'P',
+ (byte) 'R',
+ (byte) 'O',
+ (byte) 'X',
+ (byte) 'Y',
+ };
+
+ /**
+ * Binary header prefix length
+ */
+ static readonly int BINARY_PREFIX_LENGTH = BINARY_PREFIX.Length;
+
+ /**
+ * {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V1}.
+ */
+ static readonly ProtocolDetectionResult DETECTION_RESULT_V1 = ProtocolDetectionResult.Detected(HAProxyProtocolVersion.V1);
+
+ /**
+ * {@link ProtocolDetectionResult} for {@link HAProxyProtocolVersion#V2}.
+ */
+ static readonly ProtocolDetectionResult DETECTION_RESULT_V2 = ProtocolDetectionResult.Detected(HAProxyProtocolVersion.V2);
+
+ /**
+ * {@code true} if we're discarding input because we're already over maxLength
+ */
+ bool discarding;
+
+ /**
+ * Number of discarded bytes
+ */
+ int discardedBytes;
+
+ /**
+ * {@code true} if we're finished decoding the proxy protocol header
+ */
+ bool finished;
+
+ /**
+ * Protocol specification version
+ */
+ int version = -1;
+
+ /**
+ * The latest v2 spec (2014/05/18) allows for additional data to be sent in the proxy protocol header beyond the
+ * address information block so now we need a configurable max header size
+ */
+ readonly int v2MaxHeaderSize;
+
+ /**
+ * Creates a new decoder with no additional data (TLV) restrictions
+ */
+ public HAProxyMessageDecoder()
+ {
+ v2MaxHeaderSize = V2_MAX_LENGTH;
+ SingleDecode = true;
+ }
+
+ /**
+ * Creates a new decoder with restricted additional data (TLV) size
+ *
+ * Note: limiting TLV size only affects processing of v2, binary headers. Also, as allowed by the 1.5 spec
+ * TLV data is currently ignored. For maximum performance it would be best to configure your upstream proxy host to
+ * NOT send TLV data and instantiate with a max TLV size of {@code 0}.
+ *
+ *
+ * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header
+ */
+ public HAProxyMessageDecoder(int maxTlvSize)
+ {
+ SingleDecode = true;
+ if (maxTlvSize < 1)
+ {
+ v2MaxHeaderSize = V2_MIN_LENGTH;
+ }
+ else if (maxTlvSize > V2_MAX_TLV)
+ {
+ v2MaxHeaderSize = V2_MAX_LENGTH;
+ }
+ else
+ {
+ int calcMax = maxTlvSize + V2_MIN_LENGTH;
+ if (calcMax > V2_MAX_LENGTH)
+ {
+ v2MaxHeaderSize = V2_MAX_LENGTH;
+ }
+ else
+ {
+ v2MaxHeaderSize = calcMax;
+ }
+ }
+ }
+
+ /**
+ * Returns the proxy protocol specification version in the buffer if the version is found.
+ * Returns -1 if no version was found in the buffer.
+ */
+ private static int FindVersion(IByteBuffer buffer)
+ {
+ int n = buffer.ReadableBytes;
+ // per spec, the version number is found in the 13th byte
+ if (n < 13)
+ {
+ return -1;
+ }
+
+ int idx = buffer.ReaderIndex;
+ return Match(BINARY_PREFIX, buffer, idx) ? buffer.GetByte(idx + BINARY_PREFIX_LENGTH) : 1;
+ }
+
+ /**
+ * Returns the index in the buffer of the end of header if found.
+ * Returns -1 if no end of header was found in the buffer.
+ */
+ private static int FindEndOfHeader(IByteBuffer buffer)
+ {
+ int n = buffer.ReadableBytes;
+
+ // per spec, the 15th and 16th bytes contain the address length in bytes
+ if (n < 16)
+ {
+ return -1;
+ }
+
+ int offset = buffer.ReaderIndex + 14;
+
+ // the total header length will be a fixed 16 byte sequence + the dynamic address information block
+ int totalHeaderBytes = 16 + buffer.GetUnsignedShort(offset);
+
+ // ensure we actually have the full header available
+ if (n >= totalHeaderBytes)
+ {
+ return totalHeaderBytes;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ /**
+ * Returns the index in the buffer of the end of line found.
+ * Returns -1 if no end of line was found in the buffer.
+ */
+ private static int FindEndOfLine(IByteBuffer buffer)
+ {
+ int n = buffer.WriterIndex;
+ for (int i = buffer.ReaderIndex; i < n; i++)
+ {
+ byte b = buffer.GetByte(i);
+ if (b == '\r' && i < n - 1 && buffer.GetByte(i + 1) == '\n')
+ {
+ return i; // \r\n
+ }
+ }
+ return -1; // Not found.
+ }
+
+ public override void ChannelRead(IChannelHandlerContext context, object message)
+ {
+ base.ChannelRead(context, message);
+ if (finished) {
+ context.Channel.Pipeline.Remove(this);
+ }
+ }
+
+ protected override sealed void Decode(IChannelHandlerContext context, IByteBuffer input, List output)
+ {
+ // determine the specification version
+ if (version == -1 && (version = FindVersion(input)) == -1)
+ {
+ return;
+ }
+
+ IByteBuffer decoded;
+
+ if (version == 1)
+ {
+ decoded = DecodeLine(context, input);
+ }
+ else
+ {
+ decoded = DecodeStruct(context, input);
+ }
+
+ if (decoded != null)
+ {
+ finished = true;
+ try
+ {
+ if (version == 1)
+ {
+ output.Add(HAProxyMessage.DecodeHeader(decoded.ToString(Encoding.ASCII)));
+ }
+ else
+ {
+ output.Add(HAProxyMessage.DecodeHeader(decoded));
+ }
+ }
+ catch (HAProxyProtocolException e)
+ {
+ Fail(context, null, e);
+ }
+ }
+ }
+
+ /**
+ * Create a frame out of the {@link ByteBuf} and return it.
+ * Based on code from {@link LineBasedFrameDecoder#decode(ChannelHandlerContext, ByteBuf)}.
+ *
+ * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
+ * @param buffer the {@link ByteBuf} from which to read data
+ * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
+ * be created
+ */
+ private IByteBuffer DecodeStruct(IChannelHandlerContext ctx, IByteBuffer buffer)
+ {
+ int eoh = FindEndOfHeader(buffer);
+ if (!discarding)
+ {
+ if (eoh >= 0)
+ {
+ int length = eoh - buffer.ReaderIndex;
+ if (length > v2MaxHeaderSize)
+ {
+ buffer.SetReaderIndex(eoh);
+ FailOverLimit(ctx, length);
+ return null;
+ }
+ return buffer.ReadSlice(length);
+ }
+ else
+ {
+ int length = buffer.ReadableBytes;
+ if (length > v2MaxHeaderSize)
+ {
+ discardedBytes = length;
+ buffer.SkipBytes(length);
+ discarding = true;
+ FailOverLimit(ctx, "over " + discardedBytes);
+ }
+ return null;
+ }
+ }
+ else
+ {
+ if (eoh >= 0)
+ {
+ buffer.SetReaderIndex(eoh);
+ discardedBytes = 0;
+ discarding = false;
+ }
+ else
+ {
+ discardedBytes = buffer.ReadableBytes;
+ buffer.SkipBytes(discardedBytes);
+ }
+ return null;
+ }
+ }
+
+ /**
+ * Create a frame out of the {@link ByteBuf} and return it.
+ * Based on code from {@link LineBasedFrameDecoder#decode(ChannelHandlerContext, ByteBuf)}.
+ *
+ * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to
+ * @param buffer the {@link ByteBuf} from which to read data
+ * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could
+ * be created
+ */
+ private IByteBuffer DecodeLine(IChannelHandlerContext ctx, IByteBuffer buffer)
+ {
+ int eol = FindEndOfLine(buffer);
+ if (!discarding)
+ {
+ if (eol >= 0)
+ {
+ int length = eol - buffer.ReaderIndex;
+ if (length > V1_MAX_LENGTH)
+ {
+ buffer.SetReaderIndex(eol + DELIMITER_LENGTH);
+ FailOverLimit(ctx, length);
+ return null;
+ }
+ IByteBuffer frame = buffer.ReadSlice(length);
+ buffer.SkipBytes(DELIMITER_LENGTH);
+ return frame;
+ }
+ else
+ {
+ int length = buffer.ReadableBytes;
+ if (length > V1_MAX_LENGTH)
+ {
+ discardedBytes = length;
+ buffer.SkipBytes(length);
+ discarding = true;
+ FailOverLimit(ctx, "over " + discardedBytes);
+ }
+ return null;
+ }
+ }
+ else
+ {
+ if (eol >= 0)
+ {
+ int delimLength = buffer.GetByte(eol) == '\r' ? 2 : 1;
+ buffer.SetReaderIndex(eol + delimLength);
+ discardedBytes = 0;
+ discarding = false;
+ }
+ else
+ {
+ discardedBytes = buffer.ReadableBytes;
+ buffer.SkipBytes(discardedBytes);
+ }
+ return null;
+ }
+ }
+
+ private void FailOverLimit(IChannelHandlerContext ctx, int length)
+ {
+ FailOverLimit(ctx, length.ToString());
+ }
+
+ private void FailOverLimit(IChannelHandlerContext ctx, string length)
+ {
+ int maxLength = version == 1 ? V1_MAX_LENGTH : v2MaxHeaderSize;
+ Fail(ctx, "header length (" + length + ") exceeds the allowed maximum (" + maxLength + ')', null);
+ }
+
+ private void Fail(IChannelHandlerContext ctx, string errMsg, Exception e)
+ {
+ finished = true;
+ ctx.CloseAsync().Wait(); // drop connection immediately per spec
+ HAProxyProtocolException ppex;
+ if (errMsg != null && e != null)
+ {
+ ppex = new HAProxyProtocolException(errMsg, e);
+ }
+ else if (errMsg != null)
+ {
+ ppex = new HAProxyProtocolException(errMsg);
+ }
+ else if (e != null)
+ {
+ ppex = new HAProxyProtocolException(e);
+ }
+ else
+ {
+ ppex = new HAProxyProtocolException();
+ }
+ throw ppex;
+ }
+
+ /**
+ * Returns the {@link ProtocolDetectionResult} for the given {@link ByteBuf}.
+ */
+ public static ProtocolDetectionResult DetectProtocol(IByteBuffer buffer)
+ {
+ if (buffer.ReadableBytes < 12)
+ {
+ return ProtocolDetectionResult.NeedsMoreData();
+ }
+
+ int idx = buffer.ReaderIndex;
+
+ if (Match(BINARY_PREFIX, buffer, idx))
+ {
+ return DETECTION_RESULT_V2;
+ }
+ if (Match(TEXT_PREFIX, buffer, idx))
+ {
+ return DETECTION_RESULT_V1;
+ }
+ return ProtocolDetectionResult.Invalid();
+ }
+
+ private static bool Match(byte[] prefix, IByteBuffer buffer, int idx)
+ {
+ for (int i = 0; i < prefix.Length; i++)
+ {
+ byte b = buffer.GetByte(idx + i);
+ if (b != prefix[i])
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxyProtocolException.cs b/src/DotNetty.Codecs.HaProxy/HAProxyProtocolException.cs
new file mode 100644
index 000000000..1cb339a57
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxyProtocolException.cs
@@ -0,0 +1,33 @@
+namespace DotNetty.Codecs.HaProxy
+{
+ using System;
+
+ /**
+ * A {@link DecoderException} which is thrown when an invalid HAProxy proxy protocol header is encountered
+ */
+ public class HAProxyProtocolException : DecoderException
+ {
+
+ public HAProxyProtocolException()
+ : base("")
+ {
+
+ }
+
+ public HAProxyProtocolException(string message)
+ : base(message)
+ {
+ }
+
+ public HAProxyProtocolException(Exception cause)
+ : base(cause)
+ {
+ }
+
+ public HAProxyProtocolException(string message, Exception cause)
+ : base(cause)
+ {
+ }
+
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxyProtocolVersion.cs b/src/DotNetty.Codecs.HaProxy/HAProxyProtocolVersion.cs
new file mode 100644
index 000000000..d40feaa9d
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxyProtocolVersion.cs
@@ -0,0 +1,71 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy
+{
+ using System;
+ using System.Collections.Generic;
+
+ /**
+ * The HAProxy proxy protocol specification version.
+ */
+ public sealed class HAProxyProtocolVersion
+ {
+ /**
+ * The ONE proxy protocol version represents a version 1 (human-readable) header.
+ */
+ public static readonly HAProxyProtocolVersion V1 = new HAProxyProtocolVersion(HAProxyConstants.VERSION_ONE_BYTE);
+ /**
+ * The TWO proxy protocol version represents a version 2 (binary) header.
+ */
+ public static readonly HAProxyProtocolVersion V2 = new HAProxyProtocolVersion(HAProxyConstants.VERSION_TWO_BYTE);
+
+ public static IEnumerable Values
+ {
+ get
+ {
+ yield return V1;
+ yield return V2;
+ }
+ }
+
+ /**
+ * Returns the {@link HAProxyProtocolVersion} represented by the highest 4 bits of the specified byte.
+ *
+ * @param verCmdByte protocol version and command byte
+ */
+ public static HAProxyProtocolVersion ValueOf(byte verCmdByte)
+ {
+ int version = verCmdByte & VERSION_MASK;
+ switch ((byte)version)
+ {
+ case HAProxyConstants.VERSION_TWO_BYTE:
+ return V2;
+ case HAProxyConstants.VERSION_ONE_BYTE:
+ return V1;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(verCmdByte), "unknown version: " + version);
+ }
+ }
+
+ /**
+ * The highest 4 bits of the protocol version and command byte contain the version
+ */
+ const byte VERSION_MASK = (byte)0xf0;
+
+ readonly byte byteValue;
+
+ HAProxyProtocolVersion(byte byteValue)
+ {
+ this.byteValue = byteValue;
+ }
+
+ /**
+ * Returns the byte value of this command.
+ */
+ public byte ByteValue()
+ {
+ return this.byteValue;
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxyProxiedProtocol.cs b/src/DotNetty.Codecs.HaProxy/HAProxyProxiedProtocol.cs
new file mode 100644
index 000000000..cc4b27d9b
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxyProxiedProtocol.cs
@@ -0,0 +1,287 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy
+{
+ using System;
+ using System.Collections.Generic;
+
+ /**
+ * A protocol proxied by HAProxy which is represented by its transport protocol and address family.
+ */
+ public sealed class HAProxyProxiedProtocol
+ {
+ static readonly Dictionary NAME_LOOKUP = new Dictionary();
+
+ /**
+ * The UNKNOWN represents a connection which was forwarded for an unknown protocol and an unknown address family.
+ */
+ public static readonly HAProxyProxiedProtocol UNKNOWN = new HAProxyProxiedProtocol("UNKNOWN", HAProxyConstants.TPAF_UNKNOWN_BYTE, AddressFamily.AF_UNSPEC, TransportProtocol.UNSPEC);
+ /**
+ * The TCP4 represents a connection which was forwarded for an IPv4 client over TCP.
+ */
+ public static readonly HAProxyProxiedProtocol TCP4 = new HAProxyProxiedProtocol("TCP4", HAProxyConstants.TPAF_TCP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.STREAM);
+ /**
+ * The TCP6 represents a connection which was forwarded for an IPv6 client over TCP.
+ */
+ public static readonly HAProxyProxiedProtocol TCP6 = new HAProxyProxiedProtocol("TCP6", HAProxyConstants.TPAF_TCP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.STREAM);
+ /**
+ * The UDP4 represents a connection which was forwarded for an IPv4 client over UDP.
+ */
+ public static readonly HAProxyProxiedProtocol UDP4 = new HAProxyProxiedProtocol("UDP4", HAProxyConstants.TPAF_UDP4_BYTE, AddressFamily.AF_IPv4, TransportProtocol.DGRAM);
+ /**
+ * The UDP6 represents a connection which was forwarded for an IPv6 client over UDP.
+ */
+ public static readonly HAProxyProxiedProtocol UDP6 = new HAProxyProxiedProtocol("UDP6", HAProxyConstants.TPAF_UDP6_BYTE, AddressFamily.AF_IPv6, TransportProtocol.DGRAM);
+ /**
+ * The UNIX_STREAM represents a connection which was forwarded for a UNIX stream socket.
+ */
+ public static readonly HAProxyProxiedProtocol UNIX_STREAM = new HAProxyProxiedProtocol("UNIX_STREAM", HAProxyConstants.TPAF_UNIX_STREAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.STREAM);
+ /**
+ * The UNIX_DGRAM represents a connection which was forwarded for a UNIX datagram socket.
+ */
+ public static readonly HAProxyProxiedProtocol UNIX_DGRAM = new HAProxyProxiedProtocol("UNIX_DGRAM", HAProxyConstants.TPAF_UNIX_DGRAM_BYTE, AddressFamily.AF_UNIX, TransportProtocol.DGRAM);
+
+ public static IEnumerable Values
+ {
+ get
+ {
+ yield return UNKNOWN;
+ yield return TCP4;
+ yield return TCP6;
+ yield return UDP4;
+ yield return UDP6;
+ yield return UNIX_STREAM;
+ yield return UNIX_DGRAM;
+ }
+ }
+
+ readonly byte byteValue;
+ readonly AddressFamily addressFamily;
+ readonly TransportProtocol transportProtocol;
+
+ HAProxyProxiedProtocol(string name, byte byteValue, AddressFamily addressFamily, TransportProtocol transportProtocol)
+ {
+ NAME_LOOKUP.Add(name, this);
+ this.byteValue = byteValue;
+ this.addressFamily = addressFamily;
+ this.transportProtocol = transportProtocol;
+ }
+
+ /**
+ * Returns the {@link HAProxyProxiedProtocol} represented by the specified byte.
+ *
+ * @param tpafByte transport protocol and address family byte
+ */
+ public static HAProxyProxiedProtocol ValueOf(byte tpafByte)
+ {
+ switch (tpafByte)
+ {
+ case HAProxyConstants.TPAF_TCP4_BYTE:
+ return TCP4;
+ case HAProxyConstants.TPAF_TCP6_BYTE:
+ return TCP6;
+ case HAProxyConstants.TPAF_UNKNOWN_BYTE:
+ return UNKNOWN;
+ case HAProxyConstants.TPAF_UDP4_BYTE:
+ return UDP4;
+ case HAProxyConstants.TPAF_UDP6_BYTE:
+ return UDP6;
+ case HAProxyConstants.TPAF_UNIX_STREAM_BYTE:
+ return UNIX_STREAM;
+ case HAProxyConstants.TPAF_UNIX_DGRAM_BYTE:
+ return UNIX_DGRAM;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(tpafByte), "unknown transport protocol + address family: " + (tpafByte & 0xFF));
+ }
+ }
+
+ public static HAProxyProxiedProtocol ValueOf(string value)
+ {
+ HAProxyProxiedProtocol protocol;
+ NAME_LOOKUP.TryGetValue(value, out protocol);
+ if (protocol != null)
+ {
+ return protocol;
+ }
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+
+ /**
+ * Returns the byte value of this protocol and address family.
+ */
+ public byte ByteValue()
+ {
+ return this.byteValue;
+ }
+
+ /**
+ * Returns the {@link AddressFamily} of this protocol and address family.
+ */
+ public AddressFamily AddressFamilyType()
+ {
+ return this.addressFamily;
+ }
+
+ /**
+ * Returns the {@link TransportProtocol} of this protocol and address family.
+ */
+ public TransportProtocol TransportProtocolType()
+ {
+ return this.transportProtocol;
+ }
+
+ /**
+ * The address family of an HAProxy proxy protocol header.
+ */
+ public sealed class AddressFamily
+ {
+ /**
+ * The UNSPECIFIED address family represents a connection which was forwarded for an unknown protocol.
+ */
+ public static readonly AddressFamily AF_UNSPEC = new AddressFamily(HAProxyConstants.AF_UNSPEC_BYTE);
+ /**
+ * The IPV4 address family represents a connection which was forwarded for an IPV4 client.
+ */
+ public static readonly AddressFamily AF_IPv4 = new AddressFamily(HAProxyConstants.AF_IPV4_BYTE);
+ /**
+ * The IPV6 address family represents a connection which was forwarded for an IPV6 client.
+ */
+ public static readonly AddressFamily AF_IPv6 = new AddressFamily(HAProxyConstants.AF_IPV6_BYTE);
+ /**
+ * The UNIX address family represents a connection which was forwarded for a unix socket.
+ */
+ public static readonly AddressFamily AF_UNIX = new AddressFamily(HAProxyConstants.AF_UNIX_BYTE);
+
+ public static IEnumerable Values
+ {
+ get
+ {
+ yield return AF_UNSPEC;
+ yield return AF_IPv4;
+ yield return AF_IPv6;
+ yield return AF_UNIX;
+ }
+ }
+
+ /**
+ * The highest 4 bits of the transport protocol and address family byte contain the address family
+ */
+ const byte FAMILY_MASK = (byte)0xf0;
+
+ readonly byte byteValue;
+
+ /**
+ * Creates a new instance
+ */
+ AddressFamily(byte byteValue)
+ {
+ this.byteValue = byteValue;
+ }
+
+ /**
+ * Returns the {@link AddressFamily} represented by the highest 4 bits of the specified byte.
+ *
+ * @param tpafByte transport protocol and address family byte
+ */
+ public static AddressFamily ValueOf(byte tpafByte)
+ {
+ int addressFamily = tpafByte & FAMILY_MASK;
+ switch ((byte)addressFamily)
+ {
+ case HAProxyConstants.AF_IPV4_BYTE:
+ return AF_IPv4;
+ case HAProxyConstants.AF_IPV6_BYTE:
+ return AF_IPv6;
+ case HAProxyConstants.AF_UNSPEC_BYTE:
+ return AF_UNSPEC;
+ case HAProxyConstants.AF_UNIX_BYTE:
+ return AF_UNIX;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(tpafByte), "unknown address family: " + addressFamily);
+ }
+ }
+
+ /**
+ * Returns the byte value of this address family.
+ */
+ public byte ByteValue()
+ {
+ return this.byteValue;
+ }
+ }
+
+ /**
+ * The transport protocol of an HAProxy proxy protocol header
+ */
+ public sealed class TransportProtocol
+ {
+ /**
+ * The UNSPEC transport protocol represents a connection which was forwarded for an unknown protocol.
+ */
+ public static readonly TransportProtocol UNSPEC = new TransportProtocol(HAProxyConstants.TRANSPORT_UNSPEC_BYTE);
+ /**
+ * The STREAM transport protocol represents a connection which was forwarded for a TCP connection.
+ */
+ public static readonly TransportProtocol STREAM = new TransportProtocol(HAProxyConstants.TRANSPORT_STREAM_BYTE);
+ /**
+ * The DGRAM transport protocol represents a connection which was forwarded for a UDP connection.
+ */
+ public static readonly TransportProtocol DGRAM = new TransportProtocol(HAProxyConstants.TRANSPORT_DGRAM_BYTE);
+
+ public static IEnumerable Values
+ {
+ get
+ {
+ yield return UNSPEC;
+ yield return STREAM;
+ yield return DGRAM;
+ }
+ }
+
+ /**
+ * The transport protocol is specified in the lowest 4 bits of the transport protocol and address family byte
+ */
+ const byte TRANSPORT_MASK = 0x0f;
+
+ readonly byte transportByte;
+
+ /**
+ * Creates a new instance.
+ */
+ TransportProtocol(byte transportByte)
+ {
+ this.transportByte = transportByte;
+ }
+
+ /**
+ * Returns the {@link TransportProtocol} represented by the lowest 4 bits of the specified byte.
+ *
+ * @param tpafByte transport protocol and address family byte
+ */
+ public static TransportProtocol ValueOf(byte tpafByte)
+ {
+ int transportProtocol = tpafByte & TRANSPORT_MASK;
+ switch ((byte)transportProtocol)
+ {
+ case HAProxyConstants.TRANSPORT_STREAM_BYTE:
+ return STREAM;
+ case HAProxyConstants.TRANSPORT_UNSPEC_BYTE:
+ return UNSPEC;
+ case HAProxyConstants.TRANSPORT_DGRAM_BYTE:
+ return DGRAM;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(tpafByte), "unknown transport protocol: " + transportProtocol);
+ }
+ }
+
+ /**
+ * Returns the byte value of this transport protocol.
+ */
+ public byte ByteValue()
+ {
+ return this.transportByte;
+ }
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxySSLTLV.cs b/src/DotNetty.Codecs.HaProxy/HAProxySSLTLV.cs
new file mode 100644
index 000000000..d4d96bd9b
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxySSLTLV.cs
@@ -0,0 +1,76 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy
+{
+ using System.Collections.Generic;
+ using DotNetty.Buffers;
+
+ /**
+ * Represents a {@link HAProxyTLV} of the type {@link HAProxyTLV.Type#PP2_TYPE_SSL}.
+ * This TLV encapsulates other TLVs and has additional information like verification information and a client bitfield.
+ */
+ public sealed class HAProxySSLTLV : HAProxyTLV
+ {
+
+ readonly int verify;
+ readonly IList tlvs;
+ readonly byte clientBitField;
+
+ /**
+ * Creates a new HAProxySSLTLV
+ *
+ * @param verify the verification result as defined in the specification for the pp2_tlv_ssl struct (see
+ * http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt)
+ * @param clientBitField the bitfield with client information
+ * @param tlvs the encapsulated {@link HAProxyTLV}s
+ * @param rawContent the raw TLV content
+ */
+ internal HAProxySSLTLV(int verify, byte clientBitField, List tlvs, IByteBuffer rawContent) : base(Type.PP2_TYPE_SSL, (byte)0x20, rawContent)
+ {
+ this.verify = verify;
+ this.tlvs = tlvs.AsReadOnly();
+ this.clientBitField = clientBitField;
+ }
+
+ /**
+ * Returns {@code true} if the bit field for PP2_CLIENT_CERT_CONN was set
+ */
+ public bool IsPP2ClientCertConn()
+ {
+ return (this.clientBitField & 0x2) != 0;
+ }
+
+ /**
+ * Returns {@code true} if the bit field for PP2_CLIENT_SSL was set
+ */
+ public bool IsPP2ClientSSL()
+ {
+ return (this.clientBitField & 0x1) != 0;
+ }
+
+ /**
+ * Returns {@code true} if the bit field for PP2_CLIENT_CERT_SESS was set
+ */
+ public bool IsPP2ClientCertSess()
+ {
+ return (this.clientBitField & 0x4) != 0;
+ }
+
+ /**
+ * Returns the verification result
+ */
+ public int Verify()
+ {
+ return this.verify;
+ }
+
+ /**
+ * Returns an unmodifiable Set of encapsulated {@link HAProxyTLV}s.
+ */
+ public IList EncapsulatedTLVs()
+ {
+ return this.tlvs;
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/HAProxyTLV.cs b/src/DotNetty.Codecs.HaProxy/HAProxyTLV.cs
new file mode 100644
index 000000000..3f4bdc071
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/HAProxyTLV.cs
@@ -0,0 +1,102 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy
+{
+ using DotNetty.Buffers;
+
+ /**
+ * A Type-Length Value (TLV vector) that can be added to the PROXY protocol
+ * to include additional information like SSL information.
+ *
+ * @see HAProxySSLTLV
+ */
+ public class HAProxyTLV : DefaultByteBufferHolder
+ {
+
+ readonly Type type;
+ readonly byte typeByteValue;
+
+ /**
+ * The registered types a TLV can have regarding the PROXY protocol 1.5 spec
+ */
+ public enum Type
+ {
+ PP2_TYPE_ALPN,
+ PP2_TYPE_AUTHORITY,
+ PP2_TYPE_SSL,
+ PP2_TYPE_SSL_VERSION,
+ PP2_TYPE_SSL_CN,
+ PP2_TYPE_NETNS,
+ /**
+ * A TLV type that is not officially defined in the spec. May be used for nonstandard TLVs
+ */
+ OTHER
+ }
+
+ /**
+ * Returns the the {@link Type} for a specific byte value as defined in the PROXY protocol 1.5 spec
+ *
+ * If the byte value is not an official one, it will return {@link Type#OTHER}.
+ *
+ * @param byteValue the byte for a type
+ *
+ * @return the {@link Type} of a TLV
+ */
+ public static Type TypeForByteValue(byte byteValue)
+ {
+ switch (byteValue)
+ {
+ case 0x01:
+ return Type.PP2_TYPE_ALPN;
+ case 0x02:
+ return Type.PP2_TYPE_AUTHORITY;
+ case 0x20:
+ return Type.PP2_TYPE_SSL;
+ case 0x21:
+ return Type.PP2_TYPE_SSL_VERSION;
+ case 0x22:
+ return Type.PP2_TYPE_SSL_CN;
+ case 0x30:
+ return Type.PP2_TYPE_NETNS;
+ default:
+ return Type.OTHER;
+ }
+ }
+
+ /**
+ * Creates a new HAProxyTLV
+ *
+ * @param type the {@link Type} of the TLV
+ * @param typeByteValue the byteValue of the TLV. This is especially important if non-standard TLVs are used
+ * @param content the raw content of the TLV
+ */
+ internal HAProxyTLV(Type type, byte typeByteValue, IByteBuffer content) : base(content)
+ {
+ this.type = type;
+ this.typeByteValue = typeByteValue;
+ }
+
+ /**
+ * Returns the {@link Type} of this TLV
+ */
+ public Type TLVType()
+ {
+ return this.type;
+ }
+
+ /**
+ * Returns the type of the TLV as byte
+ */
+ public byte TypeByteValue()
+ {
+ return this.typeByteValue;
+ }
+
+ public override IByteBufferHolder Replace(IByteBuffer content)
+ {
+ return new HAProxyTLV(type, typeByteValue, content);
+ }
+
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/NetUtil.cs b/src/DotNetty.Codecs.HaProxy/NetUtil.cs
new file mode 100644
index 000000000..22ae9a45e
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/NetUtil.cs
@@ -0,0 +1,210 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Util
+{
+ class NetUtil
+ {
+
+ /**
+ * Takes a string and parses it to see if it is a valid IPV4 address.
+ *
+ * @return true, if the string represents an IPV4 address in dotted
+ * notation, false otherwise
+ */
+ public static bool IsValidIpV4Address(string ip)
+ {
+ return IsValidIpV4Address(ip, 0, ip.Length);
+ }
+
+ private static bool IsValidIpV4Address(string ip, int from, int toExcluded)
+ {
+ int len = toExcluded - from;
+ int i;
+ return len <= 15 && len >= 7 &&
+ (i = ip.IndexOf('.', from + 1)) > 0 && IsValidIpV4Word(ip, from, i) &&
+ (i = ip.IndexOf('.', from = i + 2)) > 0 && IsValidIpV4Word(ip, from - 1, i) &&
+ (i = ip.IndexOf('.', from = i + 2)) > 0 && IsValidIpV4Word(ip, from - 1, i) &&
+ IsValidIpV4Word(ip, i + 1, toExcluded);
+ }
+
+ private static bool IsValidIpV4Word(string word, int from, int toExclusive)
+ {
+ int len = toExclusive - from;
+ char c0, c1, c2;
+ if (len < 1 || len > 3 || (c0 = word[from]) < '0')
+ {
+ return false;
+ }
+ if (len == 3)
+ {
+ return (c1 = word[from + 1]) >= '0' &&
+ (c2 = word[from + 2]) >= '0' &&
+ (c0 <= '1' && c1 <= '9' && c2 <= '9' ||
+ c0 == '2' && c1 <= '5' && (c2 <= '5' || c1 < '5' && c2 <= '9'));
+ }
+ return c0 <= '9' && (len == 1 || IsValidNumericChar(word[from + 1]));
+ }
+
+ private static bool IsValidNumericChar(char c)
+ {
+ return c >= '0' && c <= '9';
+ }
+
+ public static bool IsValidIpV6Address(string ip)
+ {
+ int end = ip.Length;
+ if (end < 2)
+ {
+ return false;
+ }
+
+ // strip "[]"
+ int start;
+ char c = ip[0];
+ if (c == '[')
+ {
+ end--;
+ if (ip[end] != ']')
+ {
+ // must have a close ]
+ return false;
+ }
+ start = 1;
+ c = ip[1];
+ }
+ else
+ {
+ start = 0;
+ }
+
+ int colons;
+ int compressBegin;
+ if (c == ':')
+ {
+ // an IPv6 address can start with "::" or with a number
+ if (ip[start + 1] != ':')
+ {
+ return false;
+ }
+ colons = 2;
+ compressBegin = start;
+ start += 2;
+ }
+ else
+ {
+ colons = 0;
+ compressBegin = -1;
+ }
+
+ int wordLen = 0;
+ for (int i = start; i < end; i++)
+ {
+ c = ip[i];
+ if (IsValidHexChar(c))
+ {
+ if (wordLen < 4)
+ {
+ wordLen++;
+ continue;
+ }
+ return false;
+ }
+
+ switch (c)
+ {
+ case ':':
+ if (colons > 7)
+ {
+ return false;
+ }
+ if (ip[i - 1] == ':')
+ {
+ if (compressBegin >= 0)
+ {
+ return false;
+ }
+ compressBegin = i - 1;
+ }
+ else
+ {
+ wordLen = 0;
+ }
+ colons++;
+ break;
+ case '.':
+ // case for the last 32-bits represented as IPv4 x:x:x:x:x:x:d.d.d.d
+
+ // check a normal case (6 single colons)
+ if (compressBegin < 0 && colons != 6 ||
+ // a special case ::1:2:3:4:5:d.d.d.d allows 7 colons with an
+ // IPv4 ending, otherwise 7 :'s is bad
+ (colons == 7 && compressBegin >= start || colons > 7))
+ {
+ return false;
+ }
+
+ // Verify this address is of the correct structure to contain an IPv4 address.
+ // It must be IPv4-Mapped or IPv4-Compatible
+ // (see https://tools.ietf.org/html/rfc4291#section-2.5.5).
+ int ipv4Start = i - wordLen;
+ int j = ipv4Start - 2; // index of character before the previous ':'.
+ if (IsValidIPv4MappedChar(ip[j]))
+ {
+ if (!IsValidIPv4MappedChar(ip[j - 1]) ||
+ !IsValidIPv4MappedChar(ip[j - 2]) ||
+ !IsValidIPv4MappedChar(ip[j - 3]))
+ {
+ return false;
+ }
+ j -= 5;
+ }
+
+ for (; j >= start; --j)
+ {
+ char tmpChar = ip[j];
+ if (tmpChar != '0' && tmpChar != ':')
+ {
+ return false;
+ }
+ }
+
+ // 7 - is minimum IPv4 address length
+ int ipv4End = ip.IndexOf('%', ipv4Start + 7);
+ if (ipv4End < 0)
+ {
+ ipv4End = end;
+ }
+ return IsValidIpV4Address(ip, ipv4Start, ipv4End);
+ case '%':
+ // strip the interface name/index after the percent sign
+ end = i;
+ goto loopEnd;
+ default:
+ return false;
+ }
+ }
+ loopEnd:
+
+ // normal case without compression
+ if (compressBegin < 0)
+ {
+ return colons == 7 && wordLen > 0;
+ }
+
+ return compressBegin + 2 == end ||
+ // 8 colons is valid only if compression in start or end
+ wordLen > 0 && (colons < 8 || compressBegin <= start);
+ }
+
+ private static bool IsValidHexChar(char c)
+ {
+ return c >= '0' && c <= '9' || c >= 'A' && c <= 'F' || c >= 'a' && c <= 'f';
+ }
+
+ private static bool IsValidIPv4MappedChar(char c)
+ {
+ return c == 'f' || c == 'F';
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/Properties/AssemblyInfo.cs b/src/DotNetty.Codecs.HaProxy/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..bea1740fb
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/Properties/AssemblyInfo.cs
@@ -0,0 +1,8 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Reflection;
+using System.Resources;
+
+[assembly: NeutralResourcesLanguage("en-US")]
+[assembly: AssemblyMetadata("Serviceable", "True")]
\ No newline at end of file
diff --git a/src/DotNetty.Codecs.HaProxy/Properties/Friends.cs b/src/DotNetty.Codecs.HaProxy/Properties/Friends.cs
new file mode 100644
index 000000000..b0b76c274
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/Properties/Friends.cs
@@ -0,0 +1,7 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("DotNetty.Codecs.HaProxy.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100d9782d5a0b850f230f71e06de2e101d8441d83e15eef715837eee38fdbf5cb369b41ec36e6e7668c18cbb09e5419c179360461e740c1cce6ffbdcf81f245e1e705482797fe42aff2d31ecd72ea87362ded3c14066746fbab4a8e1896f8b982323c84e2c1b08407c0de18b7feef1535fb972a3b26181f5a304ebd181795a46d8f")]
+
diff --git a/src/DotNetty.Codecs.HaProxy/ProtocolDetectionResult.cs b/src/DotNetty.Codecs.HaProxy/ProtocolDetectionResult.cs
new file mode 100644
index 000000000..29a01825e
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/ProtocolDetectionResult.cs
@@ -0,0 +1,73 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs
+{
+ using System;
+
+ /**
+ * Result of detecting a protocol.
+ *
+ * @param the type of the protocol
+ */
+ public sealed class ProtocolDetectionResult
+ {
+
+ static readonly ProtocolDetectionResult NEEDS_MORE_DATE = new ProtocolDetectionResult(ProtocolDetectionState.NEEDS_MORE_DATA, default(T));
+ static readonly ProtocolDetectionResult INVALID = new ProtocolDetectionResult(ProtocolDetectionState.INVALID, default(T));
+
+ readonly ProtocolDetectionState state;
+ readonly T result;
+
+ /**
+ * Returns a {@link ProtocolDetectionResult} that signals that more data is needed to detect the protocol.
+ */
+ public static ProtocolDetectionResult NeedsMoreData()
+ {
+ return NEEDS_MORE_DATE;
+ }
+
+ /**
+ * Returns a {@link ProtocolDetectionResult} that signals the data was invalid for the protocol.
+ */
+ public static ProtocolDetectionResult Invalid()
+ {
+ return INVALID;
+ }
+
+ /**
+ * Returns a {@link ProtocolDetectionResult} which holds the detected protocol.
+ */
+ public static ProtocolDetectionResult Detected(T protocol)
+ {
+ if (protocol == null)
+ {
+ throw new ArgumentNullException(nameof(protocol));
+ }
+ return new ProtocolDetectionResult(ProtocolDetectionState.DETECTED, protocol);
+ }
+
+ private ProtocolDetectionResult(ProtocolDetectionState state, T result)
+ {
+ this.state = state;
+ this.result = result;
+ }
+
+ /**
+ * Return the {@link ProtocolDetectionState}. If the state is {@link ProtocolDetectionState#DETECTED} you
+ * can retrieve the protocol via {@link #detectedProtocol()}.
+ */
+ public ProtocolDetectionState State()
+ {
+ return this.state;
+ }
+
+ /**
+ * Returns the protocol if {@link #state()} returns {@link ProtocolDetectionState#DETECTED}, otherwise {@code null}.
+ */
+ public T DetectedProtocol()
+ {
+ return this.result;
+ }
+ }
+}
diff --git a/src/DotNetty.Codecs.HaProxy/ProtocolDetectionState.cs b/src/DotNetty.Codecs.HaProxy/ProtocolDetectionState.cs
new file mode 100644
index 000000000..a172227ef
--- /dev/null
+++ b/src/DotNetty.Codecs.HaProxy/ProtocolDetectionState.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs
+{
+ /**
+ * The state of the current detection.
+ */
+ public enum ProtocolDetectionState
+ {
+ /**
+ * Need more data to detect the protocol.
+ */
+ NEEDS_MORE_DATA,
+
+ /**
+ * The data was invalid.
+ */
+ INVALID,
+
+ /**
+ * Protocol was detected,
+ */
+ DETECTED
+ }
+}
diff --git a/src/DotNetty.Common/Utilities/NetUtil.cs b/src/DotNetty.Common/Utilities/NetUtil.cs
index b3995909d..37cb04173 100644
--- a/src/DotNetty.Common/Utilities/NetUtil.cs
+++ b/src/DotNetty.Common/Utilities/NetUtil.cs
@@ -198,6 +198,11 @@ public static bool IsValidIpV6Address(string ip)
// 8 colons is valid only if compression in start or end
wordLen > 0 && (colons < 8 || compressBegin <= start);
}
+
+ public static bool IsValidIpV4Address(string ip)
+ {
+ return IsValidIpV4Address(ip, 0, ip.Length);
+ }
static bool IsValidIpV4Address(string ip, int from, int toExcluded)
{
diff --git a/test/DotNetty.Codecs.HaProxy.Tests/DotNetty.Codecs.HaProxy.Tests.csproj b/test/DotNetty.Codecs.HaProxy.Tests/DotNetty.Codecs.HaProxy.Tests.csproj
new file mode 100644
index 000000000..1f83ad2ea
--- /dev/null
+++ b/test/DotNetty.Codecs.HaProxy.Tests/DotNetty.Codecs.HaProxy.Tests.csproj
@@ -0,0 +1,37 @@
+
+
+ true
+ netcoreapp2.0;net452
+ false
+ ../../DotNetty.snk
+ true
+
+
+ win-x64
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/DotNetty.Codecs.HaProxy.Tests/HAProxyMessageDecoderTest.cs b/test/DotNetty.Codecs.HaProxy.Tests/HAProxyMessageDecoderTest.cs
new file mode 100644
index 000000000..6d3748bff
--- /dev/null
+++ b/test/DotNetty.Codecs.HaProxy.Tests/HAProxyMessageDecoderTest.cs
@@ -0,0 +1,1051 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy.Tests
+{
+ using System.Collections.Generic;
+ using System.Text;
+ using DotNetty.Buffers;
+ using DotNetty.Transport.Channels;
+ using DotNetty.Transport.Channels.Embedded;
+ using Xunit;
+ using static DotNetty.Codecs.HaProxy.HAProxyProxiedProtocol;
+
+ public static class DecoderTestHelper
+ {
+ public static int Count(this IEnumerator enumerator)
+ {
+ int count = enumerator.Current == null ? 0 : 1;
+ while (enumerator.MoveNext())
+ {
+ count++;
+ }
+ return count;
+ }
+ }
+
+ public class HAProxyMessageDecoderTest
+ {
+
+ EmbeddedChannel ch;
+
+ public HAProxyMessageDecoderTest()
+ {
+ ch = new EmbeddedChannel(new HAProxyMessageDecoder());
+ }
+
+ private static IByteBuffer CopiedBuffer(string value, Encoding encoding)
+ {
+ return Unpooled.CopiedBuffer(value.ToCharArray(), 0, value.Length, encoding);
+ }
+
+ [Fact]
+ public void TestIPV4Decode()
+ {
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n";
+ ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V1, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.TCP4, msg.ProxiedProtocol());
+ Assert.Equal("192.168.0.1", msg.SourceAddress());
+ Assert.Equal("192.168.0.11", msg.DestinationAddress());
+ Assert.Equal(56324, msg.SourcePort());
+ Assert.Equal(443, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestIPV6Decode()
+ {
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ string header = "PROXY TCP6 2001:0db8:85a3:0000:0000:8a2e:0370:7334 1050:0:0:0:5:600:300c:326b 56324 443\r\n";
+ ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V1, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.TCP6, msg.ProxiedProtocol());
+ Assert.Equal("2001:0db8:85a3:0000:0000:8a2e:0370:7334", msg.SourceAddress());
+ Assert.Equal("1050:0:0:0:5:600:300c:326b", msg.DestinationAddress());
+ Assert.Equal(56324, msg.SourcePort());
+ Assert.Equal(443, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestUnknownProtocolDecode()
+ {
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ string header = "PROXY UNKNOWN 192.168.0.1 192.168.0.11 56324 443\r\n";
+ ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V1, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UNKNOWN, msg.ProxiedProtocol());
+ Assert.Null(msg.SourceAddress());
+ Assert.Null(msg.DestinationAddress());
+ Assert.Equal(0, msg.SourcePort());
+ Assert.Equal(0, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV1NoUDP()
+ {
+ string header = "PROXY UDP4 192.168.0.1 192.168.0.11 56324 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidPort()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 80000 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidIPV4Address()
+ {
+ string header = "PROXY TCP4 299.168.0.1 192.168.0.11 56324 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidIPV6Address()
+ {
+ string header = "PROXY TCP6 r001:0db8:85a3:0000:0000:8a2e:0370:7334 1050:0:0:0:5:600:300c:326b 56324 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidProtocol()
+ {
+ string header = "PROXY TCP7 192.168.0.1 192.168.0.11 56324 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestMissingParams()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestTooManyParams()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443 123\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidCommand()
+ {
+ string header = "PING TCP4 192.168.0.1 192.168.0.11 56324 443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestInvalidEOL()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\nGET / HTTP/1.1\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestHeaderTooLong()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 " +
+ "00000000000000000000000000000000000000000000000000000000000000000443\r\n";
+ Assert.Throws(() => ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII)));
+ }
+
+ [Fact]
+ public void TestIncompleteHeader()
+ {
+ string header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324";
+ ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII));
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestCloseOnInvalid()
+ {
+ string header = "GET / HTTP/1.1\r\n";
+ try
+ {
+ ch.WriteInbound(CopiedBuffer(header, Encoding.ASCII));
+ }
+ catch (HAProxyProtocolException ppex)
+ {
+ // swallow this exception since we're just testing to be sure the channel was closed
+ }
+ bool isComplete = ch.CloseCompletion.Wait(5000);
+ if (!isComplete || !ch.CloseCompletion.IsCompleted || ch.CloseCompletion.IsFaulted)
+ {
+ Assert.True(false, "Expected channel close");
+ }
+ }
+
+ [Fact]
+ public void TestTransportProtocolAndAddressFamily()
+ {
+ byte unknown = HAProxyProxiedProtocol.UNKNOWN.ByteValue();
+ byte tcp4 = HAProxyProxiedProtocol.TCP4.ByteValue();
+ byte tcp6 = HAProxyProxiedProtocol.TCP6.ByteValue();
+ byte udp4 = HAProxyProxiedProtocol.UDP4.ByteValue();
+ byte udp6 = HAProxyProxiedProtocol.UDP6.ByteValue();
+ byte unix_stream = HAProxyProxiedProtocol.UNIX_STREAM.ByteValue();
+ byte unix_dgram = HAProxyProxiedProtocol.UNIX_DGRAM.ByteValue();
+
+ Assert.Equal(TransportProtocol.UNSPEC, TransportProtocol.ValueOf(unknown));
+ Assert.Equal(TransportProtocol.STREAM, TransportProtocol.ValueOf(tcp4));
+ Assert.Equal(TransportProtocol.STREAM, TransportProtocol.ValueOf(tcp6));
+ Assert.Equal(TransportProtocol.STREAM, TransportProtocol.ValueOf(unix_stream));
+ Assert.Equal(TransportProtocol.DGRAM, TransportProtocol.ValueOf(udp4));
+ Assert.Equal(TransportProtocol.DGRAM, TransportProtocol.ValueOf(udp6));
+ Assert.Equal(TransportProtocol.DGRAM, TransportProtocol.ValueOf(unix_dgram));
+
+ Assert.Equal(AddressFamily.AF_UNSPEC, AddressFamily.ValueOf(unknown));
+ Assert.Equal(AddressFamily.AF_IPv4, AddressFamily.ValueOf(tcp4));
+ Assert.Equal(AddressFamily.AF_IPv4, AddressFamily.ValueOf(udp4));
+ Assert.Equal(AddressFamily.AF_IPv6, AddressFamily.ValueOf(tcp6));
+ Assert.Equal(AddressFamily.AF_IPv6, AddressFamily.ValueOf(udp6));
+ Assert.Equal(AddressFamily.AF_UNIX, AddressFamily.ValueOf(unix_stream));
+ Assert.Equal(AddressFamily.AF_UNIX, AddressFamily.ValueOf(unix_dgram));
+ }
+
+ [Fact]
+ public void TestV2IPV4Decode()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x11; // TCP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.TCP4, msg.ProxiedProtocol());
+ Assert.Equal("192.168.0.1", msg.SourceAddress());
+ Assert.Equal("192.168.0.11", msg.DestinationAddress());
+ Assert.Equal(56324, msg.SourcePort());
+ Assert.Equal(443, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2UDPDecode()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x12; // UDP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UDP4, msg.ProxiedProtocol());
+ Assert.Equal("192.168.0.1", msg.SourceAddress());
+ Assert.Equal("192.168.0.11", msg.DestinationAddress());
+ Assert.Equal(56324, msg.SourcePort());
+ Assert.Equal(443, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void Testv2IPV6Decode()
+ {
+ byte[] header = new byte[52];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x21; // TCP over IPv6
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x24; // -----
+
+ header[16] = 0x20; // Source Address
+ header[17] = 0x01; // -----
+ header[18] = 0x0d; // -----
+ header[19] = (byte)0xb8; // -----
+ header[20] = (byte)0x85; // -----
+ header[21] = (byte)0xa3; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x00; // -----
+ header[24] = 0x00; // -----
+ header[25] = 0x00; // -----
+ header[26] = (byte)0x8a; // -----
+ header[27] = 0x2e; // -----
+ header[28] = 0x03; // -----
+ header[29] = 0x70; // -----
+ header[30] = 0x73; // -----
+ header[31] = 0x34; // -----
+
+ header[32] = 0x10; // Destination Address
+ header[33] = 0x50; // -----
+ header[34] = 0x00; // -----
+ header[35] = 0x00; // -----
+ header[36] = 0x00; // -----
+ header[37] = 0x00; // -----
+ header[38] = 0x00; // -----
+ header[39] = 0x00; // -----
+ header[40] = 0x00; // -----
+ header[41] = 0x05; // -----
+ header[42] = 0x06; // -----
+ header[43] = 0x00; // -----
+ header[44] = 0x30; // -----
+ header[45] = 0x0c; // -----
+ header[46] = 0x32; // -----
+ header[47] = 0x6b; // -----
+
+ header[48] = (byte)0xdc; // Source Port
+ header[49] = 0x04; // -----
+
+ header[50] = 0x01; // Destination Port
+ header[51] = (byte)0xbb; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.TCP6, msg.ProxiedProtocol());
+ Assert.Equal("2001:db8:85a3:0:0:8a2e:370:7334", msg.SourceAddress());
+ Assert.Equal("1050:0:0:0:5:600:300c:326b", msg.DestinationAddress());
+ Assert.Equal(56324, msg.SourcePort());
+ Assert.Equal(443, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void Testv2UnixDecode()
+ {
+ byte[] header = new byte[232];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x31; // UNIX_STREAM
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = (byte)0xd8; // -----
+
+ header[16] = 0x2f; // Source Address
+ header[17] = 0x76; // -----
+ header[18] = 0x61; // -----
+ header[19] = 0x72; // -----
+ header[20] = 0x2f; // -----
+ header[21] = 0x72; // -----
+ header[22] = 0x75; // -----
+ header[23] = 0x6e; // -----
+ header[24] = 0x2f; // -----
+ header[25] = 0x73; // -----
+ header[26] = 0x72; // -----
+ header[27] = 0x63; // -----
+ header[28] = 0x2e; // -----
+ header[29] = 0x73; // -----
+ header[30] = 0x6f; // -----
+ header[31] = 0x63; // -----
+ header[32] = 0x6b; // -----
+ header[33] = 0x00; // -----
+
+ header[124] = 0x2f; // Destination Address
+ header[125] = 0x76; // -----
+ header[126] = 0x61; // -----
+ header[127] = 0x72; // -----
+ header[128] = 0x2f; // -----
+ header[129] = 0x72; // -----
+ header[130] = 0x75; // -----
+ header[131] = 0x6e; // -----
+ header[132] = 0x2f; // -----
+ header[133] = 0x64; // -----
+ header[134] = 0x65; // -----
+ header[135] = 0x73; // -----
+ header[136] = 0x74; // -----
+ header[137] = 0x2e; // -----
+ header[138] = 0x73; // -----
+ header[139] = 0x6f; // -----
+ header[140] = 0x63; // -----
+ header[141] = 0x6b; // -----
+ header[142] = 0x00; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UNIX_STREAM, msg.ProxiedProtocol());
+ Assert.Equal("/var/run/src.sock", msg.SourceAddress());
+ Assert.Equal("/var/run/dest.sock", msg.DestinationAddress());
+ Assert.Equal(0, msg.SourcePort());
+ Assert.Equal(0, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2LocalProtocolDecode()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x20; // v2, cmd=LOCAL
+ header[13] = 0x00; // Unspecified transport protocol and address family
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.LOCAL, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UNKNOWN, msg.ProxiedProtocol());
+ Assert.Null(msg.SourceAddress());
+ Assert.Null(msg.DestinationAddress());
+ Assert.Equal(0, msg.SourcePort());
+ Assert.Equal(0, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2UnknownProtocolDecode()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x00; // Unspecified transport protocol and address family
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UNKNOWN, msg.ProxiedProtocol());
+ Assert.Null(msg.SourceAddress());
+ Assert.Null(msg.DestinationAddress());
+ Assert.Equal(0, msg.SourcePort());
+ Assert.Equal(0, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2WithSslTLVs()
+ {
+ ch = new EmbeddedChannel(new HAProxyMessageDecoder());
+
+ byte[] bytes = {
+ 13, 10, 13, 10, 0, 13, 10, 81, 85, 73, 84, 10, 33, 17, 0, 35, 127, 0, 0, 1, 127, 0, 0, 1,
+ 201, 166, 7, 89, 32, 0, 20, 5, 0, 0, 0, 0, 33, 0, 5, 84, 76, 83, 118, 49, 34, 0, 4, 76, 69, 65, 70
+ };//-55, -90
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ Assert.True(ch.WriteInbound(Unpooled.CopiedBuffer(bytes)));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.TCP4, msg.ProxiedProtocol());
+ Assert.Equal("127.0.0.1", msg.SourceAddress());
+ Assert.Equal("127.0.0.1", msg.DestinationAddress());
+ Assert.Equal(51622, msg.SourcePort());
+ Assert.Equal(1881, msg.DestinationPort());
+ IList tlvs = msg.Tlvs();
+
+ Assert.Equal(3, tlvs.Count);
+ HAProxyTLV firstTlv = tlvs[0];
+ Assert.Equal(HAProxyTLV.Type.PP2_TYPE_SSL, firstTlv.TLVType());
+ HAProxySSLTLV sslTlv = (HAProxySSLTLV) firstTlv;
+ Assert.Equal(0, sslTlv.Verify());
+ Assert.True(sslTlv.IsPP2ClientSSL());
+ Assert.True(sslTlv.IsPP2ClientCertSess());
+ Assert.False(sslTlv.IsPP2ClientCertConn());
+
+ HAProxyTLV secondTlv = tlvs[1];
+
+ Assert.Equal(HAProxyTLV.Type.PP2_TYPE_SSL_VERSION, secondTlv.TLVType());
+ IByteBuffer secondContentBuf = secondTlv.Content;
+ byte[] secondContent = new byte[secondContentBuf.ReadableBytes];
+ secondContentBuf.ReadBytes(secondContent);
+ Assert.Equal(Encoding.ASCII.GetBytes("TLSv1"), secondContent);
+
+ HAProxyTLV thirdTLV = tlvs[2];
+ Assert.Equal(HAProxyTLV.Type.PP2_TYPE_SSL_CN, thirdTLV.TLVType());
+ IByteBuffer thirdContentBuf = thirdTLV.Content;
+ byte[] thirdContent = new byte[thirdContentBuf.ReadableBytes];
+ thirdContentBuf.ReadBytes(thirdContent);
+ Assert.Equal(Encoding.ASCII.GetBytes("LEAF"), thirdContent);
+
+ Assert.True(sslTlv.EncapsulatedTLVs().Contains(secondTlv));
+ Assert.True(sslTlv.EncapsulatedTLVs().Contains(thirdTLV));
+
+ Assert.True(0 < firstTlv.ReferenceCount);
+ Assert.True(0 < secondTlv.ReferenceCount);
+ Assert.True(0 < thirdTLV.ReferenceCount);
+ Assert.False(thirdTLV.Release());
+ Assert.False(secondTlv.Release());
+ Assert.True(firstTlv.Release());
+ Assert.Equal(0, firstTlv.ReferenceCount);
+ Assert.Equal(0, secondTlv.ReferenceCount);
+ Assert.Equal(0, thirdTLV.ReferenceCount);
+
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2WithTLV()
+ {
+ ch = new EmbeddedChannel(new HAProxyMessageDecoder(4));
+
+ byte[] header = new byte[236];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x31; // UNIX_STREAM
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = (byte)0xdc; // -----
+
+ header[16] = 0x2f; // Source Address
+ header[17] = 0x76; // -----
+ header[18] = 0x61; // -----
+ header[19] = 0x72; // -----
+ header[20] = 0x2f; // -----
+ header[21] = 0x72; // -----
+ header[22] = 0x75; // -----
+ header[23] = 0x6e; // -----
+ header[24] = 0x2f; // -----
+ header[25] = 0x73; // -----
+ header[26] = 0x72; // -----
+ header[27] = 0x63; // -----
+ header[28] = 0x2e; // -----
+ header[29] = 0x73; // -----
+ header[30] = 0x6f; // -----
+ header[31] = 0x63; // -----
+ header[32] = 0x6b; // -----
+ header[33] = 0x00; // -----
+
+ header[124] = 0x2f; // Destination Address
+ header[125] = 0x76; // -----
+ header[126] = 0x61; // -----
+ header[127] = 0x72; // -----
+ header[128] = 0x2f; // -----
+ header[129] = 0x72; // -----
+ header[130] = 0x75; // -----
+ header[131] = 0x6e; // -----
+ header[132] = 0x2f; // -----
+ header[133] = 0x64; // -----
+ header[134] = 0x65; // -----
+ header[135] = 0x73; // -----
+ header[136] = 0x74; // -----
+ header[137] = 0x2e; // -----
+ header[138] = 0x73; // -----
+ header[139] = 0x6f; // -----
+ header[140] = 0x63; // -----
+ header[141] = 0x6b; // -----
+ header[142] = 0x00; // -----
+
+ // ---- Additional data (TLV) ---- \\
+
+ header[232] = 0x01; // Type
+ header[233] = 0x00; // Remaining bytes
+ header[234] = 0x01; // -----
+ header[235] = 0x01; // Payload
+
+ int startChannels = ch.Pipeline.GetEnumerator().Count();
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ object msgObj = ch.ReadInbound();
+ Assert.Equal(startChannels - 1, ch.Pipeline.GetEnumerator().Count());
+ Assert.True(msgObj is HAProxyMessage);
+ HAProxyMessage msg = (HAProxyMessage)msgObj;
+ Assert.Equal(HAProxyProtocolVersion.V2, msg.ProtocolVersion());
+ Assert.Equal(HAProxyCommand.PROXY, msg.Command());
+ Assert.Equal(HAProxyProxiedProtocol.UNIX_STREAM, msg.ProxiedProtocol());
+ Assert.Equal("/var/run/src.sock", msg.SourceAddress());
+ Assert.Equal("/var/run/dest.sock", msg.DestinationAddress());
+ Assert.Equal(0, msg.SourcePort());
+ Assert.Equal(0, msg.DestinationPort());
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestV2InvalidProtocol()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x41; // Bogus transport protocol
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ Assert.Throws(() => ch.WriteInbound(Unpooled.CopiedBuffer(header)));
+ }
+
+ [Fact]
+ public void TestV2MissingParams()
+ {
+ byte[] header = new byte[26];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x11; // TCP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0a; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ Assert.Throws(() => ch.WriteInbound(Unpooled.CopiedBuffer(header)));
+ }
+
+ [Fact]
+ public void TestV2InvalidCommand()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x22; // v2, Bogus command
+ header[13] = 0x11; // TCP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ Assert.Throws(() => ch.WriteInbound(Unpooled.CopiedBuffer(header)));
+ }
+
+ [Fact]
+ public void TestV2InvalidVersion()
+ {
+ byte[] header = new byte[28];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x31; // Bogus version, cmd=PROXY
+ header[13] = 0x11; // TCP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = 0x0c; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ Assert.Throws(() => ch.WriteInbound(Unpooled.CopiedBuffer(header)));
+ }
+
+ [Fact]
+ public void TestV2HeaderTooLong()
+ {
+ ch = new EmbeddedChannel(new HAProxyMessageDecoder(0));
+
+ byte[] header = new byte[248];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+ header[13] = 0x11; // TCP over IPv4
+
+ header[14] = 0x00; // Remaining Bytes
+ header[15] = (byte)0xe8; // -----
+
+ header[16] = (byte)0xc0; // Source Address
+ header[17] = (byte)0xa8; // -----
+ header[18] = 0x00; // -----
+ header[19] = 0x01; // -----
+
+ header[20] = (byte)0xc0; // Destination Address
+ header[21] = (byte)0xa8; // -----
+ header[22] = 0x00; // -----
+ header[23] = 0x0b; // -----
+
+ header[24] = (byte)0xdc; // Source Port
+ header[25] = 0x04; // -----
+
+ header[26] = 0x01; // Destination Port
+ header[27] = (byte)0xbb; // -----
+
+ Assert.Throws(() => ch.WriteInbound(Unpooled.CopiedBuffer(header)));
+ }
+
+ [Fact]
+ public void TestV2IncompleteHeader()
+ {
+ byte[] header = new byte[13];
+ header[0] = 0x0D; // Binary Prefix
+ header[1] = 0x0A; // -----
+ header[2] = 0x0D; // -----
+ header[3] = 0x0A; // -----
+ header[4] = 0x00; // -----
+ header[5] = 0x0D; // -----
+ header[6] = 0x0A; // -----
+ header[7] = 0x51; // -----
+ header[8] = 0x55; // -----
+ header[9] = 0x49; // -----
+ header[10] = 0x54; // -----
+ header[11] = 0x0A; // -----
+
+ header[12] = 0x21; // v2, cmd=PROXY
+
+ ch.WriteInbound(Unpooled.CopiedBuffer(header));
+ Assert.Null(ch.ReadInbound());
+ Assert.False(ch.Finish());
+ }
+
+ [Fact]
+ public void TestDetectProtocol()
+ {
+ IByteBuffer validHeaderV1 = CopiedBuffer("PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n",
+ Encoding.ASCII);
+ ProtocolDetectionResult result = HAProxyMessageDecoder.DetectProtocol(validHeaderV1);
+ Assert.Equal(ProtocolDetectionState.DETECTED, result.State());
+ Assert.Equal(HAProxyProtocolVersion.V1, result.DetectedProtocol());
+ validHeaderV1.Release();
+
+ IByteBuffer invalidHeader = CopiedBuffer("Invalid header", Encoding.ASCII);
+ result = HAProxyMessageDecoder.DetectProtocol(invalidHeader);
+ Assert.Equal(ProtocolDetectionState.INVALID, result.State());
+ Assert.Null(result.DetectedProtocol());
+ invalidHeader.Release();
+
+ IByteBuffer validHeaderV2 = Unpooled.Buffer();
+ validHeaderV2.WriteByte(0x0D);
+ validHeaderV2.WriteByte(0x0A);
+ validHeaderV2.WriteByte(0x0D);
+ validHeaderV2.WriteByte(0x0A);
+ validHeaderV2.WriteByte(0x00);
+ validHeaderV2.WriteByte(0x0D);
+ validHeaderV2.WriteByte(0x0A);
+ validHeaderV2.WriteByte(0x51);
+ validHeaderV2.WriteByte(0x55);
+ validHeaderV2.WriteByte(0x49);
+ validHeaderV2.WriteByte(0x54);
+ validHeaderV2.WriteByte(0x0A);
+ result = HAProxyMessageDecoder.DetectProtocol(validHeaderV2);
+ Assert.Equal(ProtocolDetectionState.DETECTED, result.State());
+ Assert.Equal(HAProxyProtocolVersion.V2, result.DetectedProtocol());
+ validHeaderV2.Release();
+
+ IByteBuffer incompleteHeader = Unpooled.Buffer();
+ incompleteHeader.WriteByte(0x0D);
+ incompleteHeader.WriteByte(0x0A);
+ incompleteHeader.WriteByte(0x0D);
+ incompleteHeader.WriteByte(0x0A);
+ incompleteHeader.WriteByte(0x00);
+ incompleteHeader.WriteByte(0x0D);
+ incompleteHeader.WriteByte(0x0A);
+ result = HAProxyMessageDecoder.DetectProtocol(incompleteHeader);
+ Assert.Equal(ProtocolDetectionState.NEEDS_MORE_DATA, result.State());
+ Assert.Null(result.DetectedProtocol());
+ incompleteHeader.Release();
+ }
+
+ }
+}
diff --git a/test/DotNetty.Codecs.HaProxy.Tests/HAProxySSLTLVTest.cs b/test/DotNetty.Codecs.HaProxy.Tests/HAProxySSLTLVTest.cs
new file mode 100644
index 000000000..fdfeccf46
--- /dev/null
+++ b/test/DotNetty.Codecs.HaProxy.Tests/HAProxySSLTLVTest.cs
@@ -0,0 +1,51 @@
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+
+namespace DotNetty.Codecs.HaProxy.Tests
+{
+ using System.Collections.Generic;
+ using DotNetty.Buffers;
+ using Xunit;
+
+ public class HAProxySSLTLVTest
+ {
+
+ [Fact]
+ public void TestClientBitmask()
+ {
+ // 0b0000_0111
+ byte allClientsEnabled = 0x7;
+ HAProxySSLTLV allClientsEnabledTLV =
+ new HAProxySSLTLV(0, allClientsEnabled, new List(), Unpooled.Buffer());
+
+ Assert.True(allClientsEnabledTLV.IsPP2ClientCertConn());
+ Assert.True(allClientsEnabledTLV.IsPP2ClientSSL());
+ Assert.True(allClientsEnabledTLV.IsPP2ClientCertSess());
+
+ Assert.True(allClientsEnabledTLV.Release());
+
+ // 0b0000_0101
+ byte clientSSLandClientCertSessEnabled = 0x5;
+
+ HAProxySSLTLV clientSSLandClientCertSessTLV =
+ new HAProxySSLTLV(0, clientSSLandClientCertSessEnabled, new List(), Unpooled.Buffer());
+
+ Assert.False(clientSSLandClientCertSessTLV.IsPP2ClientCertConn());
+ Assert.True(clientSSLandClientCertSessTLV.IsPP2ClientSSL());
+ Assert.True(clientSSLandClientCertSessTLV.IsPP2ClientCertSess());
+
+ Assert.True(clientSSLandClientCertSessTLV.Release());
+ // 0b0000_0000
+ byte noClientEnabled = 0x0;
+
+ HAProxySSLTLV noClientTlv =
+ new HAProxySSLTLV(0, noClientEnabled, new List(), Unpooled.Buffer());
+
+ Assert.False(noClientTlv.IsPP2ClientCertConn());
+ Assert.False(noClientTlv.IsPP2ClientSSL());
+ Assert.False(noClientTlv.IsPP2ClientCertSess());
+
+ Assert.True(noClientTlv.Release());
+ }
+ }
+}
diff --git a/test/DotNetty.Codecs.HaProxy.Tests/Properties/AssemblyInfo.cs b/test/DotNetty.Codecs.HaProxy.Tests/Properties/AssemblyInfo.cs
new file mode 100644
index 000000000..83795376c
--- /dev/null
+++ b/test/DotNetty.Codecs.HaProxy.Tests/Properties/AssemblyInfo.cs
@@ -0,0 +1,19 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// General Information about an assembly is controlled through the following
+// set of attributes. Change these attribute values to modify the information
+// associated with an assembly.
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("DotNetty.Codecs.HaProxy.Tests")]
+[assembly: AssemblyTrademark("")]
+
+// Setting ComVisible to false makes the types in this assembly not visible
+// to COM components. If you need to access a type in this assembly from
+// COM, set the ComVisible attribute to true on that type.
+[assembly: ComVisible(false)]
+
+// The following GUID is for the ID of the typelib if this project is exposed to COM
+[assembly: Guid("57ab7999-1705-4f12-a05d-89fec4eaa305")]