/*
 * 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.handler.codec.haproxy.HAProxyProxiedProtocol.AddressFamily;
import io.netty.util.ByteProcessor;
import io.netty.util.CharsetUtil;
import io.netty.util.NetUtil;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

Message container for decoded HAProxy proxy protocol parameters
/** * Message container for decoded HAProxy proxy protocol parameters */
public final class HAProxyMessage {
Version 1 proxy protocol message for 'UNKNOWN' proxied protocols. Per spec, when the proxied protocol is 'UNKNOWN' we must discard all other header values.
/** * 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 HAProxyMessage V1_UNKNOWN_MSG = new HAProxyMessage( HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, HAProxyProxiedProtocol.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.
/** * 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 HAProxyMessage V2_UNKNOWN_MSG = new HAProxyMessage( HAProxyProtocolVersion.V2, HAProxyCommand.PROXY, HAProxyProxiedProtocol.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.
/** * 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 HAProxyMessage V2_LOCAL_MSG = new HAProxyMessage( HAProxyProtocolVersion.V2, HAProxyCommand.LOCAL, HAProxyProxiedProtocol.UNKNOWN, null, null, 0, 0); private final HAProxyProtocolVersion protocolVersion; private final HAProxyCommand command; private final HAProxyProxiedProtocol proxiedProtocol; private final String sourceAddress; private final String destinationAddress; private final int sourcePort; private final int destinationPort; private final List<HAProxyTLV> tlvs;
Creates a new instance
/** * Creates a new instance */
private HAProxyMessage( HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, String sourceAddress, String destinationAddress, String sourcePort, String destinationPort) { this( protocolVersion, command, proxiedProtocol, sourceAddress, destinationAddress, portStringToInt(sourcePort), portStringToInt(destinationPort)); }
Creates a new instance
/** * Creates a new instance */
private HAProxyMessage( HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) { this(protocolVersion, command, proxiedProtocol, sourceAddress, destinationAddress, sourcePort, destinationPort, Collections.<HAProxyTLV>emptyList()); }
Creates a new instance
/** * Creates a new instance */
private HAProxyMessage( HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, String sourceAddress, String destinationAddress, int sourcePort, int destinationPort, List<HAProxyTLV> tlvs) { if (proxiedProtocol == null) { throw new NullPointerException("proxiedProtocol"); } AddressFamily addrFamily = proxiedProtocol.addressFamily(); checkAddress(sourceAddress, addrFamily); checkAddress(destinationAddress, addrFamily); checkPort(sourcePort); checkPort(destinationPort); this.protocolVersion = protocolVersion; this.command = command; this.proxiedProtocol = proxiedProtocol; this.sourceAddress = sourceAddress; this.destinationAddress = destinationAddress; this.sourcePort = sourcePort; this.destinationPort = destinationPort; this.tlvs = Collections.unmodifiableList(tlvs); }
Decodes a version 2, binary proxy protocol header.
Params:
  • header – a version 2 proxy protocol header
Throws:
Returns: HAProxyMessage instance
/** * Decodes a version 2, binary proxy protocol header. * * @param header a version 2 proxy protocol header * @return {@link HAProxyMessage} instance * @throws HAProxyProtocolException if any portion of the header is invalid */
static HAProxyMessage decodeHeader(ByteBuf header) { if (header == null) { throw new NullPointerException("header"); } if (header.readableBytes() < 16) { throw new HAProxyProtocolException( "incomplete header: " + header.readableBytes() + " bytes (expected: 16+ bytes)"); } // Per spec, the 13th byte is the protocol version and command byte header.skipBytes(12); final byte verCmdByte = header.readByte(); HAProxyProtocolVersion ver; try { ver = HAProxyProtocolVersion.valueOf(verCmdByte); } catch (IllegalArgumentException e) { throw new HAProxyProtocolException(e); } if (ver != HAProxyProtocolVersion.V2) { throw new HAProxyProtocolException("version 1 unsupported: 0x" + Integer.toHexString(verCmdByte)); } HAProxyCommand cmd; try { cmd = HAProxyCommand.valueOf(verCmdByte); } catch (IllegalArgumentException e) { throw new HAProxyProtocolException(e); } if (cmd == HAProxyCommand.LOCAL) { return V2_LOCAL_MSG; } // Per spec, the 14th byte is the protocol and address family byte HAProxyProxiedProtocol protAndFam; try { protAndFam = HAProxyProxiedProtocol.valueOf(header.readByte()); } catch (IllegalArgumentException e) { throw new HAProxyProtocolException(e); } if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) { return V2_UNKNOWN_MSG; } int addressInfoLen = header.readUnsignedShort(); String srcAddress; String dstAddress; int addressLen; int srcPort = 0; int dstPort = 0; AddressFamily addressFamily = protAndFam.addressFamily(); if (addressFamily == AddressFamily.AF_UNIX) { // unix sockets require 216 bytes for address information if (addressInfoLen < 216 || header.readableBytes() < 216) { throw new HAProxyProtocolException( "incomplete UNIX socket address information: " + Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 216+ bytes)"); } int startIdx = header.readerIndex(); int addressEnd = header.forEachByte(startIdx, 108, ByteProcessor.FIND_NUL); if (addressEnd == -1) { addressLen = 108; } else { addressLen = addressEnd - startIdx; } srcAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII); startIdx += 108; addressEnd = header.forEachByte(startIdx, 108, ByteProcessor.FIND_NUL); if (addressEnd == -1) { addressLen = 108; } else { addressLen = addressEnd - startIdx; } dstAddress = header.toString(startIdx, addressLen, CharsetUtil.US_ASCII); // AF_UNIX defines that exactly 108 bytes are reserved for the address. The previous methods // did not increase the reader index although we already consumed the information. header.readerIndex(startIdx + 108); } else { if (addressFamily == AddressFamily.AF_IPv4) { // IPv4 requires 12 bytes for address information if (addressInfoLen < 12 || header.readableBytes() < 12) { throw new HAProxyProtocolException( "incomplete IPv4 address information: " + Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 12+ bytes)"); } addressLen = 4; } else if (addressFamily == AddressFamily.AF_IPv6) { // IPv6 requires 36 bytes for address information if (addressInfoLen < 36 || header.readableBytes() < 36) { throw new HAProxyProtocolException( "incomplete IPv6 address information: " + Math.min(addressInfoLen, header.readableBytes()) + " bytes (expected: 36+ bytes)"); } addressLen = 16; } else { throw new HAProxyProtocolException( "unable to parse address information (unknown 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(); } final List<HAProxyTLV> tlvs = readTlvs(header); return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort, tlvs); } private static List<HAProxyTLV> readTlvs(final ByteBuf header) { HAProxyTLV haProxyTLV = readNextTLV(header); if (haProxyTLV == null) { return Collections.emptyList(); } // In most cases there are less than 4 TLVs available List<HAProxyTLV> haProxyTLVs = new ArrayList<HAProxyTLV>(4); do { haProxyTLVs.add(haProxyTLV); if (haProxyTLV instanceof HAProxySSLTLV) { haProxyTLVs.addAll(((HAProxySSLTLV) haProxyTLV).encapsulatedTLVs()); } } while ((haProxyTLV = readNextTLV(header)) != null); return haProxyTLVs; } private static HAProxyTLV readNextTLV(final ByteBuf header) { // We need at least 4 bytes for a TLV if (header.readableBytes() < 4) { return null; } final byte typeAsByte = header.readByte(); final HAProxyTLV.Type type = HAProxyTLV.Type.typeForByteValue(typeAsByte); final int length = header.readUnsignedShort(); switch (type) { case PP2_TYPE_SSL: final ByteBuf rawContent = header.retainedSlice(header.readerIndex(), length); final ByteBuf byteBuf = header.readSlice(length); final byte client = byteBuf.readByte(); final int verify = byteBuf.readInt(); if (byteBuf.readableBytes() >= 4) { final List<HAProxyTLV> encapsulatedTlvs = new ArrayList<HAProxyTLV>(4); do { final HAProxyTLV haProxyTLV = readNextTLV(byteBuf); if (haProxyTLV == null) { break; } encapsulatedTlvs.add(haProxyTLV); } while (byteBuf.readableBytes() >= 4); return new HAProxySSLTLV(verify, client, encapsulatedTlvs, rawContent); } return new HAProxySSLTLV(verify, client, Collections.<HAProxyTLV>emptyList(), rawContent); // If we're not dealing with a SSL Type, we can use the same mechanism case PP2_TYPE_ALPN: case PP2_TYPE_AUTHORITY: case PP2_TYPE_SSL_VERSION: case PP2_TYPE_SSL_CN: case PP2_TYPE_NETNS: case OTHER: return new HAProxyTLV(type, typeAsByte, header.readRetainedSlice(length)); default: return null; } }
Decodes a version 1, human-readable proxy protocol header.
Params:
  • header – a version 1 proxy protocol header
Throws:
Returns: HAProxyMessage instance
/** * Decodes a version 1, human-readable proxy protocol header. * * @param header a version 1 proxy protocol header * @return {@link HAProxyMessage} instance * @throws HAProxyProtocolException if any portion of the header is invalid */
static HAProxyMessage decodeHeader(String header) { if (header == null) { throw new HAProxyProtocolException("header"); } String[] parts = header.split(" "); int numParts = parts.length; if (numParts < 2) { throw new HAProxyProtocolException( "invalid header: " + header + " (expected: 'PROXY' and proxied protocol values)"); } if (!"PROXY".equals(parts[0])) { throw new HAProxyProtocolException("unknown identifier: " + parts[0]); } HAProxyProxiedProtocol protAndFam; try { protAndFam = HAProxyProxiedProtocol.valueOf(parts[1]); } catch (IllegalArgumentException e) { throw new HAProxyProtocolException(e); } if (protAndFam != HAProxyProxiedProtocol.TCP4 && protAndFam != HAProxyProxiedProtocol.TCP6 && protAndFam != HAProxyProxiedProtocol.UNKNOWN) { throw new HAProxyProtocolException("unsupported v1 proxied protocol: " + parts[1]); } if (protAndFam == HAProxyProxiedProtocol.UNKNOWN) { return V1_UNKNOWN_MSG; } if (numParts != 6) { throw new HAProxyProtocolException("invalid TCP4/6 header: " + header + " (expected: 6 parts)"); } return new HAProxyMessage( HAProxyProtocolVersion.V1, HAProxyCommand.PROXY, protAndFam, parts[2], parts[3], parts[4], parts[5]); }
Convert ip address bytes to string representation
Params:
  • header – buffer containing ip address bytes
  • addressLen – number of bytes to read (4 bytes for IPv4, 16 bytes for IPv6)
Returns: string representation of the ip address
/** * 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
Params:
  • value – the port
Throws:
Returns: port as an integer
/** * Convert port to integer * * @param value the port * @return port as an integer * @throws HAProxyProtocolException if port is not a valid integer */
private static int portStringToInt(String value) { int port; try { port = Integer.parseInt(value); } catch (NumberFormatException e) { throw new HAProxyProtocolException("invalid port: " + value, e); } if (port <= 0 || port > 65535) { throw new HAProxyProtocolException("invalid port: " + value + " (expected: 1 ~ 65535)"); } return port; }
Validate an address (IPv4, IPv6, Unix Socket)
Params:
  • address – human-readable address
  • addrFamily – the AddressFamily to check the address against
Throws:
/** * Validate an address (IPv4, IPv6, Unix Socket) * * @param address human-readable address * @param addrFamily the {@link AddressFamily} to check the address against * @throws HAProxyProtocolException if the address is invalid */
private static void checkAddress(String address, AddressFamily addrFamily) { if (addrFamily == null) { throw new NullPointerException("addrFamily"); } switch (addrFamily) { case AF_UNSPEC: if (address != null) { throw new HAProxyProtocolException("unable to validate an AF_UNSPEC address: " + address); } return; case AF_UNIX: return; } if (address == null) { throw new NullPointerException("address"); } switch (addrFamily) { case AF_IPv4: if (!NetUtil.isValidIpV4Address(address)) { throw new HAProxyProtocolException("invalid IPv4 address: " + address); } break; case AF_IPv6: if (!NetUtil.isValidIpV6Address(address)) { throw new HAProxyProtocolException("invalid IPv6 address: " + address); } break; default: throw new Error(); } }
Validate a UDP/TCP port
Params:
  • port – the UDP/TCP port
Throws:
/** * Validate a UDP/TCP port * * @param port the UDP/TCP port * @throws HAProxyProtocolException if the port is out of range (0-65535 inclusive) */
private static void checkPort(int port) { if (port < 0 || port > 65535) { throw new HAProxyProtocolException("invalid port: " + port + " (expected: 1 ~ 65535)"); } } /** * Returns the {@link HAProxyProtocolVersion} of this {@link HAProxyMessage}. */ public HAProxyProtocolVersion protocolVersion() { return protocolVersion; }
Returns the HAProxyCommand of this HAProxyMessage.
/** * Returns the {@link HAProxyCommand} of this {@link HAProxyMessage}. */
public HAProxyCommand command() { return command; } /** * Returns the {@link HAProxyProxiedProtocol} of this {@link HAProxyMessage}. */ public HAProxyProxiedProtocol proxiedProtocol() { return proxiedProtocol; }
Returns the human-readable source address of this HAProxyMessage.
/** * Returns the human-readable source address of this {@link HAProxyMessage}. */
public String sourceAddress() { return sourceAddress; }
Returns the human-readable destination address of this HAProxyMessage.
/** * Returns the human-readable destination address of this {@link HAProxyMessage}. */
public String destinationAddress() { return destinationAddress; }
Returns the UDP/TCP source port of this HAProxyMessage.
/** * Returns the UDP/TCP source port of this {@link HAProxyMessage}. */
public int sourcePort() { return sourcePort; }
Returns the UDP/TCP destination port of this HAProxyMessage.
/** * Returns the UDP/TCP destination port of this {@link HAProxyMessage}. */
public int destinationPort() { return destinationPort; }
Returns a list of HAProxyTLV or an empty list if no TLVs are present.

TLVs are only available for the Proxy Protocol V2

/** * Returns a list of {@link HAProxyTLV} or an empty list if no TLVs are present. * <p> * TLVs are only available for the Proxy Protocol V2 */
public List<HAProxyTLV> tlvs() { return tlvs; } }