* Note: limiting TLV size only affects processing of v2, binary headers. Also, as allowed by the 1.5 spec * TLV data is currently ignored. For maximum performance it would be best to configure your upstream proxy host to * NOT send TLV data and instantiate with a max TLV size of {@code 0}. *
* * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header */ public HAProxyMessageDecoder(int maxTlvSize) { this(maxTlvSize, true); } /** * Creates a new decoder with restricted additional data (TLV) size, whether or not to throw an exception as soon * as we exceed maxLength. * * @param maxTlvSize maximum number of bytes allowed for additional data (Type-Length-Value vectors) in a v2 header * @param failFast Whether or not to throw an exception as soon as we exceed maxLength */ public HAProxyMessageDecoder(int maxTlvSize, boolean failFast) { if (maxTlvSize < 1) { v2MaxHeaderSize = V2_MIN_LENGTH; } else if (maxTlvSize > V2_MAX_TLV) { v2MaxHeaderSize = V2_MAX_LENGTH; } else { int calcMax = maxTlvSize + V2_MIN_LENGTH; if (calcMax > V2_MAX_LENGTH) { v2MaxHeaderSize = V2_MAX_LENGTH; } else { v2MaxHeaderSize = calcMax; } } this.failFast = failFast; } /** * 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(); return match(BINARY_PREFIX, buffer, idx) ? buffer.getByte(idx + BINARY_PREFIX_LENGTH) : 1; } /** * Returns the index in the buffer of the end of header if found. * Returns -1 if no end of header was found in the buffer. */ private static int findEndOfHeader(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) 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) { ctx.fireChannelRead(HAProxyMessage.decodeHeader(decoded.toString(CharsetUtil.US_ASCII))); } else { ctx.fireChannelRead(HAProxyMessage.decodeHeader(decoded)); } } catch (HAProxyProtocolException e) { fail(ctx, null, e); } } } /** * Create a frame out of the {@link ByteBuf} and return it. * * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to * @param buffer the {@link ByteBuf} from which to read data * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could * be created */ private ByteBuf decodeStruct(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { if (headerExtractor == null) { headerExtractor = new StructHeaderExtractor(v2MaxHeaderSize); } return headerExtractor.extract(ctx, buffer); } /** * Create a frame out of the {@link ByteBuf} and return it. * * @param ctx the {@link ChannelHandlerContext} which this {@link HAProxyMessageDecoder} belongs to * @param buffer the {@link ByteBuf} from which to read data * @return frame the {@link ByteBuf} which represent the frame or {@code null} if no frame could * be created */ private ByteBuf decodeLine(ChannelHandlerContext ctx, ByteBuf buffer) throws Exception { if (headerExtractor == null) { headerExtractor = new LineHeaderExtractor(V1_MAX_LENGTH); } return headerExtractor.extract(ctx, buffer); } 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, Exception e) { finished = true; ctx.close(); // drop connection immediately per spec HAProxyProtocolException ppex; if (errMsg != null && e != null) { ppex = new HAProxyProtocolException(errMsg, e); } else if (errMsg != null) { ppex = new HAProxyProtocolException(errMsg); } else if (e != null) { ppex = new HAProxyProtocolException(e); } else { ppex = new HAProxyProtocolException(); } throw ppex; } /** * Returns the {@link ProtocolDetectionResult} for the given {@link ByteBuf}. */ public static ProtocolDetectionResult