/* * 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; /** * 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. */ 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. */ 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. */ 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; /** * 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 */ private HAProxyMessage( HAProxyProtocolVersion protocolVersion, HAProxyCommand command, HAProxyProxiedProtocol proxiedProtocol, String sourceAddress, String destinationAddress, int sourcePort, int destinationPort) { 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; } /** * 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(); } return new HAProxyMessage(ver, cmd, protAndFam, srcAddress, dstAddress, srcPort, dstPort); } /** * 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 * * @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 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) * * @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 * * @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 {@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 {@link HAProxyMessage}. */ public String sourceAddress() { return sourceAddress; } /** * Returns the human-readable destination address of this {@link HAProxyMessage}. */ public String destinationAddress() { return destinationAddress; } /** * Returns the UDP/TCP source port of this {@link HAProxyMessage}. */ public int sourcePort() { return sourcePort; } /** * Returns the UDP/TCP destination port of this {@link HAProxyMessage}. */ public int destinationPort() { return destinationPort; } }