diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolCommand.java b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolCommand.java new file mode 100644 index 0000000000..c4f43f9ec2 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolCommand.java @@ -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 { + /** + * 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(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoder.java b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoder.java new file mode 100644 index 0000000000..ffa0dbe1d2 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoder.java @@ -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 Proxy Protocol Specification + */ +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 + *

+ * 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 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 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; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolException.java b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolException.java new file mode 100644 index 0000000000..6f11ab85a8 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolException.java @@ -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); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolMessage.java b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolMessage.java new file mode 100644 index 0000000000..3e5c7a59d2 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolMessage.java @@ -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; + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolVersion.java b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolVersion.java new file mode 100644 index 0000000000..470bc7fc43 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/HAProxyProtocolVersion.java @@ -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 { + /** + * 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(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedAddressFamily.java b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedAddressFamily.java new file mode 100644 index 0000000000..e1605baad6 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedAddressFamily.java @@ -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 { + /** + * 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(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedProtocolAndFamily.java b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedProtocolAndFamily.java new file mode 100644 index 0000000000..e2b2534645 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedProtocolAndFamily.java @@ -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 { + /** + * 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 PROTO_AND_FAMILY_NAME_MAP = + new HashMap(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()); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedTransportProtocol.java b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedTransportProtocol.java new file mode 100644 index 0000000000..51916f0c0d --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/ProxiedTransportProtocol.java @@ -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 { + /** + * 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(); + } +} diff --git a/codec/src/main/java/io/netty/handler/codec/haproxy/package-info.java b/codec/src/main/java/io/netty/handler/codec/haproxy/package-info.java new file mode 100644 index 0000000000..6fdd68eed5 --- /dev/null +++ b/codec/src/main/java/io/netty/handler/codec/haproxy/package-info.java @@ -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 Proxy Protocol Specification + */ +package io.netty.handler.codec.haproxy; diff --git a/codec/src/test/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoderTest.java b/codec/src/test/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoderTest.java new file mode 100644 index 0000000000..c39359fef0 --- /dev/null +++ b/codec/src/test/java/io/netty/handler/codec/haproxy/HAProxyProtocolDecoderTest.java @@ -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()); + } +}