Add HAProxy protocol decoder

Motivation:

The proxy protocol provides client connection information for proxied
network services. Several implementations exist (e.g. Haproxy, Stunnel,
Stud, Postfix), but the primary motivation for this implementation is to
support the proxy protocol feature of Amazon Web Services Elastic Load
Balancing.

Modifications:

This commit adds a proxy protocol decoder for proxy protocol version 1
as specified at:

  http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt

The foundation for version 2 support is also in this commit but it is
explicitly NOT supported due to a lack of external implementations to
test against.

Result:

The proxy protocol decoder can be used to send client connection
information to inbound handlers in a channel pipeline from services
which support the proxy protocol.
This commit is contained in:
Jon Keys 2014-04-22 22:25:10 -04:00 committed by Trustin Lee
parent 87346d14a8
commit bea5d0321a
10 changed files with 2445 additions and 0 deletions

View File

@ -0,0 +1,115 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
/**
* The command of an HAProxy proxy protocol header
*/
public final class HAProxyProtocolCommand implements Comparable<HAProxyProtocolCommand> {
/**
* The command is specified in the lowest 4 bits of the protocol version and command byte
*/
private static final byte COMMAND_MASK = (byte) 0x0f;
/**
* Version byte constants
*/
private static final byte LOCAL_BYTE = (byte) 0x00;
private static final byte PROXY_BYTE = (byte) 0x01;
/**
* The LOCAL command represents a connection that was established on purpose by the proxy
* without being relayed
*/
public static final HAProxyProtocolCommand LOCAL = new HAProxyProtocolCommand("LOCAL", LOCAL_BYTE);
/**
* The PROXY command represents a connection that was established on behalf of another node,
* and reflects the original connection endpoints
*/
public static final HAProxyProtocolCommand PROXY = new HAProxyProtocolCommand("PROXY", PROXY_BYTE);
private final String name;
private final byte cmdByte;
/**
* Creates a new instance
*/
private HAProxyProtocolCommand(String name, byte cmdByte) {
this.name = name;
this.cmdByte = cmdByte;
}
/**
* Returns the {@link HAProxyProtocolCommand} represented by the specified protocol version and command byte
*
* @param verCmdByte protocol version and command byte
* @return {@link HAProxyProtocolCommand} instance OR {@code null} if the command is not recognized
*/
public static HAProxyProtocolCommand valueOf(byte verCmdByte) {
switch ((byte) (verCmdByte & COMMAND_MASK)) {
case PROXY_BYTE:
return PROXY;
case LOCAL_BYTE:
return LOCAL;
default:
return null;
}
}
/**
* Returns the name of this command
*
* @return the name of this command
*/
public String name() {
return name;
}
/**
* Returns the byte value of this command
*
* @return the byte value of this command
*/
public byte byteValue() {
return cmdByte;
}
@Override
public int hashCode() {
return byteValue();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof HAProxyProtocolCommand)) {
return false;
}
HAProxyProtocolCommand that = (HAProxyProtocolCommand) o;
return byteValue() == that.byteValue();
}
@Override
public String toString() {
return name();
}
@Override
public int compareTo(HAProxyProtocolCommand o) {
return byteValue() - o.byteValue();
}
}

View File

@ -0,0 +1,361 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import io.netty.util.CharsetUtil;
import java.util.List;
/**
* Decodes an HAProxy proxy protocol header
*
* @see <a href="http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
*/
public class HAProxyProtocolDecoder extends ByteToMessageDecoder {
/**
* Maximum possible length of a v1 proxy protocol header per spec
*/
private static final int V1_MAX_LENGTH = 108;
/**
* Maximum possible length of a v2 proxy protocol header (fixed 16 bytes + max unsigned short)
*/
private static final int V2_MAX_LENGTH = 16 + 65535;
/**
* Minimum possible length of a fully functioning v2 proxy protocol header (fixed 16 bytes + v2 address info space)
*/
private static final int V2_MIN_LENGTH = 16 + 216;
/**
* Maximum possible length for v2 additional TLV data (max unsigned short - max v2 address info space)
*/
private static final int V2_MAX_TLV = 65535 - 216;
/**
* Version 1 header delimiter is always '\r\n' per spec
*/
private static final int DELIMITER_LENGTH = 2;
/**
* Binary header prefix
*/
private static final byte[] BINARY_PREFIX = new byte[] {
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x00,
(byte) 0x0D,
(byte) 0x0A,
(byte) 0x51,
(byte) 0x55,
(byte) 0x49,
(byte) 0x54,
(byte) 0x0A
};
/**
* Binary header prefix length
*/
private static final int BINARY_PREFIX_LENGTH = BINARY_PREFIX.length;
/**
* {@code true} if we're discarding input because we're already over maxLength
*/
private boolean discarding;
/**
* Number of discarded bytes
*/
private int discardedBytes;
/**
* {@code true} if we're finished decoding the proxy protocol header
*/
private boolean finished;
/**
* Protocol specification version
*/
private 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
*/
private int v2MaxHeaderSize;
/**
* Creates a new decoder with no additional data (TLV) restrictions
*/
public HAProxyProtocolDecoder() {
this.v2MaxHeaderSize = V2_MAX_LENGTH;
}
/**
* Creates a new decoder with restricted additional data (TLV) size
* <p>
* <b>Note:</b> 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
* <b>NOT</b> send TLV data and instantiate with a max TLV size of {@code 0}.
* </p>
*
* @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header
*/
public HAProxyProtocolDecoder(int maxTlvSize) {
if (maxTlvSize < 1) {
this.v2MaxHeaderSize = V2_MIN_LENGTH;
} else if (maxTlvSize > V2_MAX_TLV) {
this.v2MaxHeaderSize = V2_MAX_LENGTH;
} else {
int calcMax = maxTlvSize + V2_MIN_LENGTH;
if (calcMax > V2_MAX_LENGTH) {
this.v2MaxHeaderSize = V2_MAX_LENGTH;
} else {
this.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(final ByteBuf buffer) {
final int n = buffer.readableBytes();
// per spec, the version number is found in the 13th byte
if (n < 13) {
return -1;
}
int idx = buffer.readerIndex();
for (int i = 0; i < BINARY_PREFIX_LENGTH; i++) {
final byte b = buffer.getByte(idx + i);
if (b != BINARY_PREFIX[i]) {
return 1;
}
}
return buffer.getByte(idx + BINARY_PREFIX_LENGTH);
}
/**
* 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(final ByteBuf buffer) {
final 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(final ByteBuf buffer) {
final int n = buffer.writerIndex();
for (int i = buffer.readerIndex(); i < n; i++) {
final byte b = buffer.getByte(i);
if (b == '\r' && i < n - 1 && buffer.getByte(i + 1) == '\n') {
return i; // \r\n
}
}
return -1; // Not found.
}
@Override
public boolean isSingleDecode() {
// ByteToMessageDecoder uses this method to optionally break out of the decoding loop after each unit of work.
// Since we only ever want to decode a single header we always return true to save a bit of work here.
return true;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
super.channelRead(ctx, msg);
if (finished) {
ctx.pipeline().remove(this);
}
}
@Override
protected final void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
// determine the specification version
if (version == -1) {
if ((version = findVersion(in)) == -1) {
return;
}
}
ByteBuf decoded;
if (version == 1) {
decoded = decodeLine(ctx, in);
} else {
decoded = decodeStruct(ctx, in);
}
if (decoded != null) {
finished = true;
try {
if (version == 1) {
out.add(HAProxyProtocolMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII)));
} else {
out.add(HAProxyProtocolMessage.decodeHeader(decoded));
}
} catch (HAProxyProtocolException e) {
fail(ctx, 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 HAProxyProtocolDecoder} 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 ByteBuf decodeStruct(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
final int eoh = findEndOfHeader(buffer);
if (!discarding) {
if (eoh >= 0) {
final int length = eoh - buffer.readerIndex();
if (length > v2MaxHeaderSize) {
buffer.readerIndex(eoh);
failOverLimit(ctx, length);
return null;
}
return buffer.readSlice(length);
} else {
final int length = buffer.readableBytes();
if (length > v2MaxHeaderSize) {
discardedBytes = length;
buffer.skipBytes(length);
discarding = true;
failOverLimit(ctx, "over " + discardedBytes);
}
return null;
}
} else {
if (eoh >= 0) {
final int length = discardedBytes + eoh - buffer.readerIndex();
buffer.readerIndex(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 HAProxyProtocolDecoder} 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 ByteBuf decodeLine(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception {
final int eol = findEndOfLine(buffer);
if (!discarding) {
if (eol >= 0) {
final int length = eol - buffer.readerIndex();
if (length > V1_MAX_LENGTH) {
buffer.readerIndex(eol + DELIMITER_LENGTH);
failOverLimit(ctx, length);
return null;
}
ByteBuf frame = buffer.readSlice(length);
buffer.skipBytes(DELIMITER_LENGTH);
return frame;
} else {
final 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) {
final int length = discardedBytes + eol - buffer.readerIndex();
final int delimLength = buffer.getByte(eol) == '\r' ? 2 : 1;
buffer.readerIndex(eol + delimLength);
discardedBytes = 0;
discarding = false;
} else {
discardedBytes = buffer.readableBytes();
buffer.skipBytes(discardedBytes);
}
return null;
}
}
private void failOverLimit(final ChannelHandlerContext ctx, int length) {
failOverLimit(ctx, String.valueOf(length));
}
private void failOverLimit(final ChannelHandlerContext 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(final ChannelHandlerContext ctx, String errMsg, Throwable t) {
finished = true;
ctx.close(); // drop connection immediately per spec
HAProxyProtocolException ppex;
if (errMsg != null && t != null) {
ppex = new HAProxyProtocolException(errMsg, t);
} else if (errMsg != null) {
ppex = new HAProxyProtocolException(errMsg);
} else if (t != null) {
ppex = new HAProxyProtocolException(t);
} else {
ppex = new HAProxyProtocolException();
}
throw ppex;
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
import io.netty.handler.codec.DecoderException;
/**
* A {@link DecoderException} which is thrown when an invalid HAProxy proxy protocol header is encountered
*/
public class HAProxyProtocolException extends DecoderException {
private static final long serialVersionUID = 713710864325167351L;
/**
* Creates a new instance
*/
public HAProxyProtocolException() {
}
/**
* Creates a new instance
*/
public HAProxyProtocolException(String message, Throwable cause) {
super(message, cause);
}
/**
* Creates a new instance
*/
public HAProxyProtocolException(String message) {
super(message);
}
/**
* Creates a new instance
*/
public HAProxyProtocolException(Throwable cause) {
super(cause);
}
}

View File

@ -0,0 +1,415 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufProcessor;
import io.netty.util.internal.StringUtil;
import io.netty.util.NetUtil;
import java.nio.charset.Charset;
/**
* Message container for decoded HAProxy proxy protocol parameters
*/
public final class HAProxyProtocolMessage {
/**
* The default system character encoding
*/
private static final Charset SYSTEM_CHARSET = Charset.defaultCharset();
/**
* Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
* 'UNKNOWN' we must discard all other header values.
*/
private static final HAProxyProtocolMessage V1_UNKNOWN_MSG = new HAProxyProtocolMessage(HAProxyProtocolVersion.ONE,
HAProxyProtocolCommand.PROXY, ProxiedProtocolAndFamily.UNKNOWN, null, null, 0, 0);
/**
* Version 2 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is
* 'UNKNOWN' we must discard all other header values.
*/
private static final HAProxyProtocolMessage V2_UNKNOWN_MSG = new HAProxyProtocolMessage(HAProxyProtocolVersion.TWO,
HAProxyProtocolCommand.PROXY, ProxiedProtocolAndFamily.UNKNOWN, null, null, 0, 0);
/**
* Version 2 proxy protocol message for local requests. Per spec, we should use an unspecified protocol and family
* for 'LOCAL' commands. Per spec, when the proxied protocol is 'UNKNOWN' we must discard all other header values.
*/
private static final HAProxyProtocolMessage V2_LOCAL_MSG = new HAProxyProtocolMessage(HAProxyProtocolVersion.TWO,
HAProxyProtocolCommand.LOCAL, ProxiedProtocolAndFamily.UNKNOWN, null, null, 0, 0);
private final HAProxyProtocolVersion version;
private final HAProxyProtocolCommand command;
private final ProxiedProtocolAndFamily paf;
private final String sourceAddress;
private final String destinationAddress;
private final int sourcePort;
private final int destinationPort;
/**
* Creates a new instance
*/
private HAProxyProtocolMessage(HAProxyProtocolVersion ver, HAProxyProtocolCommand cmd, ProxiedProtocolAndFamily paf,
String srcAddress, String dstAddress, String srcPort, String dstPort) {
this(ver, cmd, paf, srcAddress, dstAddress, portStringToInt(srcPort), portStringToInt(dstPort));
}
/**
* Creates a new instance
*/
private HAProxyProtocolMessage(HAProxyProtocolVersion ver, HAProxyProtocolCommand cmd, ProxiedProtocolAndFamily paf,
String srcAddress, String dstAddress, int srcPort, int dstPort) {
ProxiedAddressFamily addrFamily;
if (paf != null) {
addrFamily = paf.proxiedAddressFamily();
} else {
addrFamily = null;
}
checkAddress(srcAddress, addrFamily);
checkAddress(dstAddress, addrFamily);
checkPort(srcPort);
checkPort(dstPort);
this.version = ver;
this.command = cmd;
this.paf = paf;
this.sourceAddress = srcAddress;
this.destinationAddress = dstAddress;
this.sourcePort = srcPort;
this.destinationPort = dstPort;
}
/**
* Decode a version 2, binary proxy protocol header
*
* @param header a version 2 proxy protocol header
* @return {@link HAProxyProtocolMessage} instance
* @throws HAProxyProtocolException if any portion of the header is invalid
*/
static HAProxyProtocolMessage decodeHeader(ByteBuf header) throws HAProxyProtocolException {
if (header == null) {
throw new HAProxyProtocolException("null header");
}
if (header.readableBytes() < 16) {
throw new HAProxyProtocolException("incomplete header (header must be at least 16 bytes)");
}
// Per spec, the 13th byte is the protocol version and command byte
header.skipBytes(12);
final byte verCmdByte = header.readByte();
HAProxyProtocolVersion ver = HAProxyProtocolVersion.valueOf(verCmdByte);
if (ver == null || !HAProxyProtocolVersion.TWO.equals(ver)) {
throw new HAProxyProtocolException("unsupported header version 0x" + Integer.toHexString(verCmdByte));
}
HAProxyProtocolCommand cmd = HAProxyProtocolCommand.valueOf(verCmdByte);
if (cmd == null) {
throw new HAProxyProtocolException("unkown command 0x" + Integer.toHexString(verCmdByte));
}
if (HAProxyProtocolCommand.LOCAL.equals(cmd)) {
return V2_LOCAL_MSG;
}
// Per spec, the 14th byte is the protocol and address family byte
ProxiedProtocolAndFamily protAndFam = ProxiedProtocolAndFamily.valueOf(header.readByte());
if (protAndFam == null) {
throw new HAProxyProtocolException("unkown protocol and family");
}
if (ProxiedProtocolAndFamily.UNKNOWN.equals(protAndFam)) {
return V2_UNKNOWN_MSG;
}
int addressInfoLen = header.readUnsignedShort();
String srcAddress;
String dstAddress;
int addressLen;
int srcPort = 0;
int dstPort = 0;
ProxiedAddressFamily addressFamily = protAndFam.proxiedAddressFamily();
if (ProxiedAddressFamily.UNIX.equals(addressFamily)) {
// unix sockets require 216 bytes for address information
if (addressInfoLen < 216 || header.readableBytes() < 216) {
throw new HAProxyProtocolException(
"incomplete address information (unix socket address info must be at least 216 bytes)");
}
int startIdx = header.readerIndex();
int addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
if (addressEnd == -1) {
addressLen = 108;
} else {
addressLen = addressEnd - startIdx;
}
srcAddress = header.toString(startIdx, addressLen, SYSTEM_CHARSET);
startIdx = startIdx + 108;
addressEnd = header.forEachByte(startIdx, 108, ByteBufProcessor.FIND_NUL);
if (addressEnd == -1) {
addressLen = 108;
} else {
addressLen = addressEnd - startIdx;
}
dstAddress = header.toString(startIdx, addressLen, SYSTEM_CHARSET);
} else {
if (ProxiedAddressFamily.IPV4.equals(addressFamily)) {
// IPv4 requires 12 bytes for address information
if (addressInfoLen < 12 || header.readableBytes() < 12) {
throw new HAProxyProtocolException(
"incomplete address information (IPv4 address info must be at least 12 bytes)");
}
addressLen = 4;
} else if (ProxiedAddressFamily.IPV6.equals(addressFamily)) {
// IPv6 requires 36 bytes for address information
if (addressInfoLen < 36 || header.readableBytes() < 36) {
throw new HAProxyProtocolException(
"incomplete address information (IPv6 address info must be at least 36 bytes)");
}
addressLen = 16;
} else {
throw new HAProxyProtocolException(
"unable to parse address information (unkown address family " + addressFamily + ")");
}
// Per spec, the src address begins at the 17th byte
srcAddress = ipBytestoString(header, addressLen);
dstAddress = ipBytestoString(header, addressLen);
srcPort = header.readUnsignedShort();
dstPort = header.readUnsignedShort();
}
return new HAProxyProtocolMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort);
}
/**
* Decode a version 1, human-readable proxy protocol header
*
* @param header a version 1 proxy protocol header
* @return {@link HAProxyProtocolMessage} instance
* @throws HAProxyProtocolException if any portion of the header is invalid
*/
static HAProxyProtocolMessage decodeHeader(String header) throws HAProxyProtocolException {
if (header == null) {
throw new HAProxyProtocolException("null header");
}
String[] parts = StringUtil.split(header, ' ');
int numParts = parts.length;
if (numParts < 2) {
throw new HAProxyProtocolException(
"invalid format (header must at least contain protocol and proxied protocol values)");
}
if (!"PROXY".equals(parts[0])) {
throw new HAProxyProtocolException("unsupported protocol " + parts[0]);
}
ProxiedProtocolAndFamily protAndFam = ProxiedProtocolAndFamily.valueOf(parts[1]);
boolean validPaf = protAndFam != null &&
(ProxiedProtocolAndFamily.TCP4.equals(protAndFam) || ProxiedProtocolAndFamily.TCP6.equals(protAndFam) ||
ProxiedProtocolAndFamily.UNKNOWN.equals(protAndFam));
if (!validPaf) {
throw new HAProxyProtocolException("unsupported v1 proxied protocol " + parts[1]);
}
if (ProxiedProtocolAndFamily.UNKNOWN.equals(protAndFam)) {
return V1_UNKNOWN_MSG;
}
if (numParts != 6) {
throw new HAProxyProtocolException("invalid format (header must contain exactly 6 values for TCP proxies)");
}
return new HAProxyProtocolMessage(HAProxyProtocolVersion.ONE, HAProxyProtocolCommand.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(ByteBuf 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(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
sb.append(":");
sb.append(Integer.toHexString(header.readUnsignedShort()));
}
return sb.toString();
}
/**
* Convert port to integer
*
* @param port the port
* @return port as integer
* @throws HAProxyProtocolException if port is not a valid integer
*/
private static int portStringToInt(String port) throws HAProxyProtocolException {
try {
return Integer.parseInt(port);
} catch (NumberFormatException e) {
throw new HAProxyProtocolException(port + " is not a valid port", e);
}
}
/**
* Validate an address (IPv4, IPv6, Unix Socket)
*
* @param address human-readable address
* @param addrFamily the {@link ProxiedAddressFamily} to check the address against
* @throws HAProxyProtocolException if the address is invalid
*/
private static void checkAddress(String address, ProxiedAddressFamily addrFamily) throws HAProxyProtocolException {
if (addrFamily == null) {
throw new HAProxyProtocolException("unable to validate address because no address family is set");
}
if (ProxiedAddressFamily.UNSPECIFIED.equals(addrFamily) && address != null) {
throw new HAProxyProtocolException(
"unable to validate address because address family is " + addrFamily);
}
if (ProxiedAddressFamily.UNIX.equals(addrFamily)) {
return;
}
boolean isValid = true;
if (ProxiedAddressFamily.IPV4.equals(addrFamily)) {
isValid = NetUtil.isValidIpV4Address(address);
} else if (ProxiedAddressFamily.IPV6.equals(addrFamily)) {
isValid = NetUtil.isValidIpV6Address(address);
}
if (!isValid) {
throw new HAProxyProtocolException(address + " is not a valid " + addrFamily + " address");
}
}
/**
* 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) throws HAProxyProtocolException {
if (port < 0 || port > 65535) {
throw new HAProxyProtocolException(port + " is not a valid port");
}
}
/**
* Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyProtocolMessage}
*
* @return the proxy protocol specification version
*/
public HAProxyProtocolVersion version() {
return version;
}
/**
* Returns the {@link HAProxyProtocolCommand} of this {@link HAProxyProtocolMessage}
*
* @return the proxy protocol command
*/
public HAProxyProtocolCommand command() {
return command;
}
/**
* Returns the {@link ProxiedProtocolAndFamily} of this {@link HAProxyProtocolMessage}
*
* @return the proxied protocol and address family
*/
public ProxiedProtocolAndFamily protocolAndFamily() {
return paf;
}
/**
* Returns the human-readable source address of this {@link HAProxyProtocolMessage}
*
* @return the human-readable source address
*/
public String sourceAddress() {
return sourceAddress;
}
/**
* Returns the human-readable destination address of this {@link HAProxyProtocolMessage}
*
* @return the human-readable destination address
*/
public String destinationAddress() {
return destinationAddress;
}
/**
* Returns the UDP/TCP source port of this {@link HAProxyProtocolMessage}
*
* @return the UDP/TCP source port
*/
public int sourcePort() {
return sourcePort;
}
/**
* Returns the UDP/TCP destination port of this {@link HAProxyProtocolMessage}
*
* @return the UDP/TCP destination port
*/
public int destinationPort() {
return destinationPort;
}
}

View File

@ -0,0 +1,114 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
/**
* The HAProxy proxy protocol specification version
*/
public final class HAProxyProtocolVersion implements Comparable<HAProxyProtocolVersion> {
/**
* The highest 4 bits of the protocol version and command byte contain the version
*/
private static final byte VERSION_MASK = (byte) 0xf0;
/**
* Version byte constants
*/
private static final byte ONE_BYTE = (byte) 0x10;
private static final byte TWO_BYTE = (byte) 0x20;
/**
* The ONE proxy protocol version represents a version 1 (human-readable) header
*/
public static final HAProxyProtocolVersion ONE = new HAProxyProtocolVersion("ONE", ONE_BYTE);
/**
* The TWO proxy protocol version represents a version 2 (binary) header
*/
public static final HAProxyProtocolVersion TWO = new HAProxyProtocolVersion("TWO", TWO_BYTE);
private final String name;
private final byte versionByte;
/**
* Creates a new instance
*/
private HAProxyProtocolVersion(String name, byte versionByte) {
this.name = name;
this.versionByte = versionByte;
}
/**
* Returns the {@link HAProxyProtocolVersion} represented by the specified protocol version and command byte
*
* @param verCmdByte protocol version and command byte
* @return {@link HAProxyProtocolVersion} instance OR {@code null} if the
* version is not recognized
*/
public static HAProxyProtocolVersion valueOf(byte verCmdByte) {
switch ((byte) (verCmdByte & VERSION_MASK)) {
case TWO_BYTE:
return TWO;
case ONE_BYTE:
return ONE;
default:
return null;
}
}
/**
* Returns the name of this version
*
* @return the name of this version
*/
public String name() {
return name;
}
/**
* Returns the byte value of this version
*
* @return the byte value of this version
*/
public byte byteValue() {
return versionByte;
}
@Override
public int hashCode() {
return byteValue();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof HAProxyProtocolVersion)) {
return false;
}
HAProxyProtocolVersion that = (HAProxyProtocolVersion) o;
return byteValue() == that.byteValue();
}
@Override
public String toString() {
return name();
}
@Override
public int compareTo(HAProxyProtocolVersion o) {
return byteValue() - o.byteValue();
}
}

View File

@ -0,0 +1,130 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
/**
* The address family of an HAProxy proxy protocol header
*/
public final class ProxiedAddressFamily implements Comparable<ProxiedAddressFamily> {
/**
* The highest 4 bits of the transport protocol and address family byte contain the address family
*/
private static final byte FAMILY_MASK = (byte) 0xf0;
/**
* Address family byte constants
*/
private static final byte UNSPECIFIED_BYTE = (byte) 0x00;
private static final byte IPV4_BYTE = (byte) 0x10;
private static final byte IPV6_BYTE = (byte) 0x20;
private static final byte UNIX_BYTE = (byte) 0x30;
/**
* The UNSPECIFIED address family represents a connection which was forwarded for an unkown protocol
*/
public static final ProxiedAddressFamily UNSPECIFIED = new ProxiedAddressFamily("UNSPECIFIED", UNSPECIFIED_BYTE);
/**
* The IPV4 address family represents a connection which was forwarded for an IPV4 client
*/
public static final ProxiedAddressFamily IPV4 = new ProxiedAddressFamily("IPV4", IPV4_BYTE);
/**
* The IPV6 address family represents a connection which was forwarded for an IPV6 client
*/
public static final ProxiedAddressFamily IPV6 = new ProxiedAddressFamily("IPV6", IPV6_BYTE);
/**
* The UNIX address family represents a connection which was forwarded for a unix socket
*/
public static final ProxiedAddressFamily UNIX = new ProxiedAddressFamily("UNIX", UNIX_BYTE);
private final String name;
private final byte addressFamilyByte;
/**
* Creates a new instance
*/
private ProxiedAddressFamily(String name, byte addressFamilyByte) {
this.name = name;
this.addressFamilyByte = addressFamilyByte;
}
/**
* Returns the {@link ProxiedAddressFamily} represented by the specified address family byte
*
* @param addressFamilyByte address family byte
* @return {@link ProxiedAddressFamily} instance OR {@code null} if the
* address family is not recognized
*/
public static ProxiedAddressFamily valueOf(byte addressFamilyByte) {
switch((byte) (addressFamilyByte & FAMILY_MASK)) {
case IPV4_BYTE:
return IPV4;
case IPV6_BYTE:
return IPV6;
case UNSPECIFIED_BYTE:
return UNSPECIFIED;
case UNIX_BYTE:
return UNIX;
default:
return null;
}
}
/**
* Returns the name of this address family
*
* @return the name of this address family
*/
public String name() {
return name;
}
/**
* Returns the byte value of this address family
*
* @return the byte value of this address family
*/
public byte byteValue() {
return addressFamilyByte;
}
@Override
public int hashCode() {
return byteValue();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ProxiedAddressFamily)) {
return false;
}
ProxiedAddressFamily that = (ProxiedAddressFamily) o;
return byteValue() == that.byteValue();
}
@Override
public String toString() {
return name();
}
@Override
public int compareTo(ProxiedAddressFamily o) {
return byteValue() - o.byteValue();
}
}

View File

@ -0,0 +1,207 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
import java.util.HashMap;
import java.util.Map;
/**
* The protocol and address family of an HAProxy proxy protocol header
*/
public final class ProxiedProtocolAndFamily implements Comparable<ProxiedProtocolAndFamily> {
/**
* Protocol and address family byte constants
*/
private static final byte UNKNOWN_BYTE = (byte) 0x00;
private static final byte TCP4_BYTE = (byte) 0x11;
private static final byte TCP6_BYTE = (byte) 0x21;
private static final byte UDP4_BYTE = (byte) 0x12;
private static final byte UDP6_BYTE = (byte) 0x22;
private static final byte UNIX_STREAM_BYTE = (byte) 0x31;
private static final byte UNIX_DGRAM_BYTE = (byte) 0x32;
/**
* The UNKNOWN protocol and address family represents a connection which was forwarded for an unknown protocol
* and address family
*/
public static final ProxiedProtocolAndFamily UNKNOWN = new ProxiedProtocolAndFamily(
"UNKNOWN", ProxiedAddressFamily.UNSPECIFIED, ProxiedTransportProtocol.UNSPECIFIED, UNKNOWN_BYTE);
/**
* The TCP4 protocol and address family represents a connection which was forwarded for an IPV4 client over TCP
*/
public static final ProxiedProtocolAndFamily TCP4 = new ProxiedProtocolAndFamily(
"TCP4", ProxiedAddressFamily.IPV4, ProxiedTransportProtocol.STREAM, TCP4_BYTE);
/**
* The TCP6 protocol and address family represents a connection which was forwarded for an IPV6 client over TCP
*/
public static final ProxiedProtocolAndFamily TCP6 = new ProxiedProtocolAndFamily(
"TCP6", ProxiedAddressFamily.IPV6, ProxiedTransportProtocol.STREAM, TCP6_BYTE);
/**
* The UDP4 protocol and address family represents a connection which was forwarded for an IPV4 client over UDP
*/
public static final ProxiedProtocolAndFamily UDP4 = new ProxiedProtocolAndFamily(
"UDP4", ProxiedAddressFamily.IPV4, ProxiedTransportProtocol.DGRAM, UDP4_BYTE);
/**
* The UDP6 protocol and address family represents a connection which was forwarded for an IPV6 client over UDP
*/
public static final ProxiedProtocolAndFamily UDP6 = new ProxiedProtocolAndFamily(
"UDP6", ProxiedAddressFamily.IPV6, ProxiedTransportProtocol.DGRAM, UDP6_BYTE);
/**
* The UNIX_STREAM protocol and address family represents a connection which was forwarded for a unix stream socket
*/
public static final ProxiedProtocolAndFamily UNIX_STREAM = new ProxiedProtocolAndFamily(
"UNIX_STREAM", ProxiedAddressFamily.UNIX, ProxiedTransportProtocol.STREAM, UNIX_STREAM_BYTE);
/**
* The UNIX_DGRAM protocol and address family represents a connection which was forwarded for a unix datagram socket
*/
public static final ProxiedProtocolAndFamily UNIX_DGRAM = new ProxiedProtocolAndFamily(
"UNIX_DGRAM", ProxiedAddressFamily.UNIX, ProxiedTransportProtocol.DGRAM, UNIX_DGRAM_BYTE);
private static final Map<String, ProxiedProtocolAndFamily> PROTO_AND_FAMILY_NAME_MAP =
new HashMap<String, ProxiedProtocolAndFamily>(7);
static {
PROTO_AND_FAMILY_NAME_MAP.put(UNKNOWN.name(), UNKNOWN);
PROTO_AND_FAMILY_NAME_MAP.put(TCP4.name(), TCP4);
PROTO_AND_FAMILY_NAME_MAP.put(TCP6.name(), TCP6);
PROTO_AND_FAMILY_NAME_MAP.put(UDP4.name(), UDP4);
PROTO_AND_FAMILY_NAME_MAP.put(UDP6.name(), UDP6);
PROTO_AND_FAMILY_NAME_MAP.put(UNIX_STREAM.name(), UNIX_STREAM);
PROTO_AND_FAMILY_NAME_MAP.put(UNIX_DGRAM.name(), UNIX_DGRAM);
}
private final String name;
private final byte pafByte;
private final ProxiedAddressFamily addressFamily;
private final ProxiedTransportProtocol transportProtocol;
/**
* Creates a new instance
*/
private ProxiedProtocolAndFamily(String name, ProxiedAddressFamily addressFamily,
ProxiedTransportProtocol transportProtocol, byte pafByte) {
this.name = name;
this.pafByte = pafByte;
this.addressFamily = addressFamily;
this.transportProtocol = transportProtocol;
}
/**
* Returns the {@link ProxiedProtocolAndFamily} represented by the specified name
*
* @param name protocol and address family name
* @return {@link ProxiedProtocolAndFamily} instance OR {@code null} if the
* name is not recognized
*/
public static ProxiedProtocolAndFamily valueOf(String name) {
return PROTO_AND_FAMILY_NAME_MAP.get(name);
}
/**
* Returns the {@link ProxiedProtocolAndFamily} represented by the protocol and family byte
*
* @param pafByte protocol and address family byte
* @return {@link ProxiedProtocolAndFamily} instance OR {@code null} if the
* protocol and address family byte is not recognized
*/
public static ProxiedProtocolAndFamily valueOf(byte pafByte) {
switch (pafByte) {
case TCP4_BYTE:
return TCP4;
case TCP6_BYTE:
return TCP6;
case UNKNOWN_BYTE:
return UNKNOWN;
case UDP4_BYTE:
return UDP4;
case UDP6_BYTE:
return UDP6;
case UNIX_STREAM_BYTE:
return UNIX_STREAM;
case UNIX_DGRAM_BYTE:
return UNIX_DGRAM;
default:
return null;
}
}
/**
* Returns the name of this protocol and address family
*
* @return the name of this protocol and address family
*/
public String name() {
return name;
}
/**
* Returns the byte value of this protocol and address family
*
* @return the byte value of this protocol and address family
*/
public byte byteValue() {
return pafByte;
}
/**
* Returns the {@link ProxiedAddressFamily} of this protocol and address family
*
* @return the address family
*/
public ProxiedAddressFamily proxiedAddressFamily() {
return addressFamily;
}
/**
* Returns the {@link ProxiedTransportProtocol} of this protocol and address family
*
* @return the transport protocol
*/
public ProxiedTransportProtocol proxiedTransportProtocol() {
return transportProtocol;
}
@Override
public int hashCode() {
return name().hashCode();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ProxiedProtocolAndFamily)) {
return false;
}
ProxiedProtocolAndFamily that = (ProxiedProtocolAndFamily) o;
return name().equals(that.name());
}
@Override
public String toString() {
return name();
}
@Override
public int compareTo(ProxiedProtocolAndFamily o) {
return name().compareTo(o.name());
}
}

View File

@ -0,0 +1,123 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
/**
* The transport protocol of an HAProxy proxy protocol header
*/
public final class ProxiedTransportProtocol implements Comparable<ProxiedTransportProtocol> {
/**
* The transport protocol is specified in the lowest 4 bits of the transport protocol and address family byte
*/
private static final byte TRANSPORT_MASK = (byte) 0x0f;
/**
* Transport Protocol byte constants
*/
private static final byte UNSPECIFIED_BYTE = (byte) 0x00;
private static final byte STREAM_BYTE = (byte) 0x01;
private static final byte DGRAM_BYTE = (byte) 0x02;
/**
* The UNSPECIFIED transport protocol represents a connection which was forwarded for an unkown protocol
*/
public static final ProxiedTransportProtocol UNSPECIFIED = new ProxiedTransportProtocol(
"UNSPECIFIED", UNSPECIFIED_BYTE);
/**
* The STREAM transport protocol represents a connection which was forwarded for a TCP connection
*/
public static final ProxiedTransportProtocol STREAM = new ProxiedTransportProtocol("STREAM", STREAM_BYTE);
/**
* The DGRAM transport protocol represents a connection which was forwarded for a UDP connection
*/
public static final ProxiedTransportProtocol DGRAM = new ProxiedTransportProtocol("DGRAM", DGRAM_BYTE);
private final String name;
private final byte transportByte;
/**
* Creates a new instance
*/
private ProxiedTransportProtocol(String name, byte transportByte) {
this.name = name;
this.transportByte = transportByte;
}
/**
* Returns the {@link ProxiedTransportProtocol} represented by the specified transport protocol byte
*
* @param addressFamilyByte transport protocol byte
* @return {@link ProxiedTransportProtocol} instance OR {@code null} if the
* transport protocol is not recognized
*/
public static ProxiedTransportProtocol valueOf(byte transportByte) {
switch ((byte) (transportByte & TRANSPORT_MASK)) {
case STREAM_BYTE:
return STREAM;
case UNSPECIFIED_BYTE:
return UNSPECIFIED;
case DGRAM_BYTE:
return DGRAM;
default:
return null;
}
}
/**
* Returns the name of this transport protocol
*
* @return the name of this transport protocol
*/
public String name() {
return name;
}
/**
* Returns the byte value of this transport protocol
*
* @return the byte value of this transport protocol
*/
public byte byteValue() {
return transportByte;
}
@Override
public int hashCode() {
return byteValue();
}
@Override
public boolean equals(Object o) {
if (!(o instanceof ProxiedTransportProtocol)) {
return false;
}
ProxiedTransportProtocol that = (ProxiedTransportProtocol) o;
return byteValue() == that.byteValue();
}
@Override
public String toString() {
return name();
}
@Override
public int compareTo(ProxiedTransportProtocol o) {
return byteValue() - o.byteValue();
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
/**
* Decodes an HAProxy proxy protocol header
*
* @see <a href="http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt">Proxy Protocol Specification</a>
*/
package io.netty.handler.codec.haproxy;

View File

@ -0,0 +1,905 @@
/*
* Copyright 2014 The Netty Project
*
* The Netty Project licenses this file to you under the Apache License,
* version 2.0 (the "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at:
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package io.netty.handler.codec.haproxy;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.embedded.EmbeddedChannel;
import io.netty.util.CharsetUtil;
import org.junit.Before;
import org.junit.Test;
import static io.netty.buffer.Unpooled.*;
import static org.junit.Assert.*;
public class HAProxyProtocolDecoderTest {
private EmbeddedChannel ch;
@Before
public void setUp() {
ch = new EmbeddedChannel(new HAProxyProtocolDecoder());
}
@Test
public void testIPV4Decode() {
int startChannels = ch.pipeline().names().size();
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.ONE, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.TCP4, msg.protocolAndFamily());
assertEquals("192.168.0.1", msg.sourceAddress());
assertEquals("192.168.0.11", msg.destinationAddress());
assertEquals(56324, msg.sourcePort());
assertEquals(443, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testIPV6Decode() {
int startChannels = ch.pipeline().names().size();
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, CharsetUtil.US_ASCII));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.ONE, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.TCP6, msg.protocolAndFamily());
assertEquals("2001:0db8:85a3:0000:0000:8a2e:0370:7334", msg.sourceAddress());
assertEquals("1050:0:0:0:5:600:300c:326b", msg.destinationAddress());
assertEquals(56324, msg.sourcePort());
assertEquals(443, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testUnknownProtocolDecode() {
int startChannels = ch.pipeline().names().size();
String header = "PROXY UNKNOWN 192.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.ONE, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.UNKNOWN, msg.protocolAndFamily());
assertNull(msg.sourceAddress());
assertNull(msg.destinationAddress());
assertEquals(0, msg.sourcePort());
assertEquals(0, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test(expected = HAProxyProtocolException.class)
public void testV1NoUDP() {
String header = "PROXY UDP4 192.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testInvalidPort() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 80000 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testInvalidIPV4Address() {
String header = "PROXY TCP4 299.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
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";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testInvalidProtocol() {
String header = "PROXY TCP7 192.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testMissingParams() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testTooManyParams() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443 123\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testInvalidCommand() {
String header = "PING TCP4 192.168.0.1 192.168.0.11 56324 443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testInvalidEOL() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 443\nGET / HTTP/1.1\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test(expected = HAProxyProtocolException.class)
public void testHeaderTooLong() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324 " +
"00000000000000000000000000000000000000000000000000000000000000000443\r\n";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
}
@Test
public void testIncompleteHeader() {
String header = "PROXY TCP4 192.168.0.1 192.168.0.11 56324";
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testCloseOnInvalid() {
ChannelFuture closeFuture = ch.closeFuture();
String header = "GET / HTTP/1.1\r\n";
try {
ch.writeInbound(copiedBuffer(header, CharsetUtil.US_ASCII));
} catch (HAProxyProtocolException ppex) {
// swallow this exception since we're just testing to be sure the channel was closed
}
boolean isComplete = closeFuture.awaitUninterruptibly(5000);
if (!isComplete || !closeFuture.isDone() || !closeFuture.isSuccess()) {
fail("Expected channel close");
}
}
@Test
public void testTransportProtocolAndAddressFamily() {
final byte unkown = ProxiedProtocolAndFamily.UNKNOWN.byteValue();
final byte tcp4 = ProxiedProtocolAndFamily.TCP4.byteValue();
final byte tcp6 = ProxiedProtocolAndFamily.TCP6.byteValue();
final byte udp4 = ProxiedProtocolAndFamily.UDP4.byteValue();
final byte udp6 = ProxiedProtocolAndFamily.UDP6.byteValue();
final byte unix_stream = ProxiedProtocolAndFamily.UNIX_STREAM.byteValue();
final byte unix_dgram = ProxiedProtocolAndFamily.UNIX_DGRAM.byteValue();
assertEquals(ProxiedTransportProtocol.UNSPECIFIED, ProxiedTransportProtocol.valueOf(unkown));
assertEquals(ProxiedTransportProtocol.STREAM, ProxiedTransportProtocol.valueOf(tcp4));
assertEquals(ProxiedTransportProtocol.STREAM, ProxiedTransportProtocol.valueOf(tcp6));
assertEquals(ProxiedTransportProtocol.STREAM, ProxiedTransportProtocol.valueOf(unix_stream));
assertEquals(ProxiedTransportProtocol.DGRAM, ProxiedTransportProtocol.valueOf(udp4));
assertEquals(ProxiedTransportProtocol.DGRAM, ProxiedTransportProtocol.valueOf(udp6));
assertEquals(ProxiedTransportProtocol.DGRAM, ProxiedTransportProtocol.valueOf(unix_dgram));
assertEquals(ProxiedAddressFamily.UNSPECIFIED, ProxiedAddressFamily.valueOf(unkown));
assertEquals(ProxiedAddressFamily.IPV4, ProxiedAddressFamily.valueOf(tcp4));
assertEquals(ProxiedAddressFamily.IPV4, ProxiedAddressFamily.valueOf(udp4));
assertEquals(ProxiedAddressFamily.IPV6, ProxiedAddressFamily.valueOf(tcp6));
assertEquals(ProxiedAddressFamily.IPV6, ProxiedAddressFamily.valueOf(udp6));
assertEquals(ProxiedAddressFamily.UNIX, ProxiedAddressFamily.valueOf(unix_stream));
assertEquals(ProxiedAddressFamily.UNIX, ProxiedAddressFamily.valueOf(unix_dgram));
}
@Test
public void testV2IPV4Decode() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x11; // TCP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.TCP4, msg.protocolAndFamily());
assertEquals("192.168.0.1", msg.sourceAddress());
assertEquals("192.168.0.11", msg.destinationAddress());
assertEquals(56324, msg.sourcePort());
assertEquals(443, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testV2UDPDecode() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x12; // UDP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.UDP4, msg.protocolAndFamily());
assertEquals("192.168.0.1", msg.sourceAddress());
assertEquals("192.168.0.11", msg.destinationAddress());
assertEquals(56324, msg.sourcePort());
assertEquals(443, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testv2IPV6Decode() {
byte[] header = new byte[52];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x21; // TCP over IPv6
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x24; // -----
header[16] = (byte) 0x20; // Source Address
header[17] = (byte) 0x01; // -----
header[18] = (byte) 0x0d; // -----
header[19] = (byte) 0xb8; // -----
header[20] = (byte) 0x85; // -----
header[21] = (byte) 0xa3; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x00; // -----
header[24] = (byte) 0x00; // -----
header[25] = (byte) 0x00; // -----
header[26] = (byte) 0x8a; // -----
header[27] = (byte) 0x2e; // -----
header[28] = (byte) 0x03; // -----
header[29] = (byte) 0x70; // -----
header[30] = (byte) 0x73; // -----
header[31] = (byte) 0x34; // -----
header[32] = (byte) 0x10; // Destination Address
header[33] = (byte) 0x50; // -----
header[34] = (byte) 0x00; // -----
header[35] = (byte) 0x00; // -----
header[36] = (byte) 0x00; // -----
header[37] = (byte) 0x00; // -----
header[38] = (byte) 0x00; // -----
header[39] = (byte) 0x00; // -----
header[40] = (byte) 0x00; // -----
header[41] = (byte) 0x05; // -----
header[42] = (byte) 0x06; // -----
header[43] = (byte) 0x00; // -----
header[44] = (byte) 0x30; // -----
header[45] = (byte) 0x0c; // -----
header[46] = (byte) 0x32; // -----
header[47] = (byte) 0x6b; // -----
header[48] = (byte) 0xdc; // Source Port
header[49] = (byte) 0x04; // -----
header[50] = (byte) 0x01; // Destination Port
header[51] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.TCP6, msg.protocolAndFamily());
assertEquals("2001:db8:85a3:0:0:8a2e:370:7334", msg.sourceAddress());
assertEquals("1050:0:0:0:5:600:300c:326b", msg.destinationAddress());
assertEquals(56324, msg.sourcePort());
assertEquals(443, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testv2UnixDecode() {
byte[] header = new byte[232];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x31; // UNIX_STREAM
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0xd8; // -----
header[16] = (byte) 0x2f; // Source Address
header[17] = (byte) 0x76; // -----
header[18] = (byte) 0x61; // -----
header[19] = (byte) 0x72; // -----
header[20] = (byte) 0x2f; // -----
header[21] = (byte) 0x72; // -----
header[22] = (byte) 0x75; // -----
header[23] = (byte) 0x6e; // -----
header[24] = (byte) 0x2f; // -----
header[25] = (byte) 0x73; // -----
header[26] = (byte) 0x72; // -----
header[27] = (byte) 0x63; // -----
header[28] = (byte) 0x2e; // -----
header[29] = (byte) 0x73; // -----
header[30] = (byte) 0x6f; // -----
header[31] = (byte) 0x63; // -----
header[32] = (byte) 0x6b; // -----
header[33] = (byte) 0x00; // -----
header[124] = (byte) 0x2f; // Destination Address
header[125] = (byte) 0x76; // -----
header[126] = (byte) 0x61; // -----
header[127] = (byte) 0x72; // -----
header[128] = (byte) 0x2f; // -----
header[129] = (byte) 0x72; // -----
header[130] = (byte) 0x75; // -----
header[131] = (byte) 0x6e; // -----
header[132] = (byte) 0x2f; // -----
header[133] = (byte) 0x64; // -----
header[134] = (byte) 0x65; // -----
header[135] = (byte) 0x73; // -----
header[136] = (byte) 0x74; // -----
header[137] = (byte) 0x2e; // -----
header[138] = (byte) 0x73; // -----
header[139] = (byte) 0x6f; // -----
header[140] = (byte) 0x63; // -----
header[141] = (byte) 0x6b; // -----
header[142] = (byte) 0x00; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.UNIX_STREAM, msg.protocolAndFamily());
assertEquals("/var/run/src.sock", msg.sourceAddress());
assertEquals("/var/run/dest.sock", msg.destinationAddress());
assertEquals(0, msg.sourcePort());
assertEquals(0, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testV2LocalProtocolDecode() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x20; // v2, cmd=LOCAL
header[13] = (byte) 0x00; // Unspecified transport protocol and address family
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.LOCAL, msg.command());
assertEquals(ProxiedProtocolAndFamily.UNKNOWN, msg.protocolAndFamily());
assertNull(msg.sourceAddress());
assertNull(msg.destinationAddress());
assertEquals(0, msg.sourcePort());
assertEquals(0, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testV2UnknownProtocolDecode() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x00; // Unspecified transport protocol and address family
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.UNKNOWN, msg.protocolAndFamily());
assertNull(msg.sourceAddress());
assertNull(msg.destinationAddress());
assertEquals(0, msg.sourcePort());
assertEquals(0, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test
public void testV2WithTLV() {
ch = new EmbeddedChannel(new HAProxyProtocolDecoder(4));
byte[] header = new byte[236];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x31; // UNIX_STREAM
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0xdc; // -----
header[16] = (byte) 0x2f; // Source Address
header[17] = (byte) 0x76; // -----
header[18] = (byte) 0x61; // -----
header[19] = (byte) 0x72; // -----
header[20] = (byte) 0x2f; // -----
header[21] = (byte) 0x72; // -----
header[22] = (byte) 0x75; // -----
header[23] = (byte) 0x6e; // -----
header[24] = (byte) 0x2f; // -----
header[25] = (byte) 0x73; // -----
header[26] = (byte) 0x72; // -----
header[27] = (byte) 0x63; // -----
header[28] = (byte) 0x2e; // -----
header[29] = (byte) 0x73; // -----
header[30] = (byte) 0x6f; // -----
header[31] = (byte) 0x63; // -----
header[32] = (byte) 0x6b; // -----
header[33] = (byte) 0x00; // -----
header[124] = (byte) 0x2f; // Destination Address
header[125] = (byte) 0x76; // -----
header[126] = (byte) 0x61; // -----
header[127] = (byte) 0x72; // -----
header[128] = (byte) 0x2f; // -----
header[129] = (byte) 0x72; // -----
header[130] = (byte) 0x75; // -----
header[131] = (byte) 0x6e; // -----
header[132] = (byte) 0x2f; // -----
header[133] = (byte) 0x64; // -----
header[134] = (byte) 0x65; // -----
header[135] = (byte) 0x73; // -----
header[136] = (byte) 0x74; // -----
header[137] = (byte) 0x2e; // -----
header[138] = (byte) 0x73; // -----
header[139] = (byte) 0x6f; // -----
header[140] = (byte) 0x63; // -----
header[141] = (byte) 0x6b; // -----
header[142] = (byte) 0x00; // -----
// ---- Additional data (TLV) ---- \\
header[232] = (byte) 0x01; // Type
header[233] = (byte) 0x00; // Remaining bytes
header[234] = (byte) 0x01; // -----
header[235] = (byte) 0x01; // Payload
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
Object msgObj = ch.readInbound();
assertEquals(startChannels - 1, ch.pipeline().names().size());
assertTrue(msgObj instanceof HAProxyProtocolMessage);
HAProxyProtocolMessage msg = (HAProxyProtocolMessage) msgObj;
assertEquals(HAProxyProtocolVersion.TWO, msg.version());
assertEquals(HAProxyProtocolCommand.PROXY, msg.command());
assertEquals(ProxiedProtocolAndFamily.UNIX_STREAM, msg.protocolAndFamily());
assertEquals("/var/run/src.sock", msg.sourceAddress());
assertEquals("/var/run/dest.sock", msg.destinationAddress());
assertEquals(0, msg.sourcePort());
assertEquals(0, msg.destinationPort());
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
@Test(expected = HAProxyProtocolException.class)
public void testV2InvalidProtocol() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x41; // Bogus transport protocol
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
}
@Test(expected = HAProxyProtocolException.class)
public void testV2MissingParams() {
byte[] header = new byte[26];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x11; // TCP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0a; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
}
@Test(expected = HAProxyProtocolException.class)
public void testV2InvalidCommand() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x22; // v2, Bogus command
header[13] = (byte) 0x11; // TCP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
}
@Test(expected = HAProxyProtocolException.class)
public void testV2InvalidVersion() {
byte[] header = new byte[28];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x31; // Bogus version, cmd=PROXY
header[13] = (byte) 0x11; // TCP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0x0c; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
}
@Test(expected = HAProxyProtocolException.class)
public void testV2HeaderTooLong() {
ch = new EmbeddedChannel(new HAProxyProtocolDecoder(0));
byte[] header = new byte[248];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
header[13] = (byte) 0x11; // TCP over IPv4
header[14] = (byte) 0x00; // Remaining Bytes
header[15] = (byte) 0xe8; // -----
header[16] = (byte) 0xc0; // Source Address
header[17] = (byte) 0xa8; // -----
header[18] = (byte) 0x00; // -----
header[19] = (byte) 0x01; // -----
header[20] = (byte) 0xc0; // Destination Address
header[21] = (byte) 0xa8; // -----
header[22] = (byte) 0x00; // -----
header[23] = (byte) 0x0b; // -----
header[24] = (byte) 0xdc; // Source Port
header[25] = (byte) 0x04; // -----
header[26] = (byte) 0x01; // Destination Port
header[27] = (byte) 0xbb; // -----
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
}
@Test
public void testV2IncompleteHeader() {
byte[] header = new byte[13];
header[0] = (byte) 0x0D; // Binary Prefix
header[1] = (byte) 0x0A; // -----
header[2] = (byte) 0x0D; // -----
header[3] = (byte) 0x0A; // -----
header[4] = (byte) 0x00; // -----
header[5] = (byte) 0x0D; // -----
header[6] = (byte) 0x0A; // -----
header[7] = (byte) 0x51; // -----
header[8] = (byte) 0x55; // -----
header[9] = (byte) 0x49; // -----
header[10] = (byte) 0x54; // -----
header[11] = (byte) 0x0A; // -----
header[12] = (byte) 0x21; // v2, cmd=PROXY
int startChannels = ch.pipeline().names().size();
ch.writeInbound(copiedBuffer(header));
assertNull(ch.readInbound());
assertFalse(ch.finish());
}
}