//
//  ========================================================================
//  Copyright (c) 1995-2019 Mort Bay Consulting Pty. Ltd.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server;

import java.io.IOException;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ReadPendingException;
import java.nio.channels.WritePendingException;
import java.nio.charset.StandardCharsets;
import java.util.Iterator;

import org.eclipse.jetty.io.AbstractConnection;
import org.eclipse.jetty.io.Connection;
import org.eclipse.jetty.io.EndPoint;
import org.eclipse.jetty.util.AttributesMap;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.Callback;
import org.eclipse.jetty.util.TypeUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

ConnectionFactory for the PROXY Protocol.

This factory can be placed in front of any other connection factory to process the proxy v1 or v2 line before the normal protocol handling

See Also:
/** * <p>ConnectionFactory for the PROXY Protocol.</p> * <p>This factory can be placed in front of any other connection factory * to process the proxy v1 or v2 line before the normal protocol handling</p> * * @see <a href="http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt">http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt</a> */
public class ProxyConnectionFactory extends AbstractConnectionFactory { private static final Logger LOG = Log.getLogger(ProxyConnectionFactory.class); public static final String TLS_VERSION = "TLS_VERSION"; private final String _next; private int _maxProxyHeader = 1024;
Proxy Connection Factory that uses the next ConnectionFactory on the connector as the next protocol
/** * Proxy Connection Factory that uses the next ConnectionFactory * on the connector as the next protocol */
public ProxyConnectionFactory() { super("proxy"); _next = null; } public ProxyConnectionFactory(String nextProtocol) { super("proxy"); _next = nextProtocol; } public int getMaxProxyHeader() { return _maxProxyHeader; } public void setMaxProxyHeader(int maxProxyHeader) { _maxProxyHeader = maxProxyHeader; } @Override public Connection newConnection(Connector connector, EndPoint endp) { String next = _next; if (next == null) { for (Iterator<String> i = connector.getProtocols().iterator(); i.hasNext(); ) { String p = i.next(); if (getProtocol().equalsIgnoreCase(p)) { next = i.next(); break; } } } return new ProxyProtocolV1orV2Connection(endp, connector, next); } public class ProxyProtocolV1orV2Connection extends AbstractConnection { private final Connector _connector; private final String _next; private ByteBuffer _buffer = BufferUtil.allocate(16); protected ProxyProtocolV1orV2Connection(EndPoint endp, Connector connector, String next) { super(endp, connector.getExecutor()); _connector = connector; _next = next; } @Override public void onOpen() { super.onOpen(); fillInterested(); } @Override public void onFillable() { try { while (BufferUtil.space(_buffer) > 0) { // Read data int fill = getEndPoint().fill(_buffer); if (fill < 0) { getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } } // Is it a V1? switch (_buffer.get(0)) { case 'P': { ProxyProtocolV1Connection v1 = new ProxyProtocolV1Connection(getEndPoint(), _connector, _next, _buffer); getEndPoint().upgrade(v1); return; } case 0x0D: { ProxyProtocolV2Connection v2 = new ProxyProtocolV2Connection(getEndPoint(), _connector, _next, _buffer); getEndPoint().upgrade(v2); return; } default: LOG.warn("Not PROXY protocol for {}", getEndPoint()); close(); } } catch (Throwable x) { LOG.warn("PROXY error for " + getEndPoint(), x); close(); } } } public static class ProxyProtocolV1Connection extends AbstractConnection { // 0 1 2 3 4 5 6 // 98765432109876543210987654321 // PROXY P R.R.R.R L.L.L.L R Lrn private static final int[] SIZE = {29, 23, 21, 13, 5, 3, 1}; private final Connector _connector; private final String _next; private final StringBuilder _builder = new StringBuilder(); private final String[] _field = new String[6]; private int _fields; private int _length; protected ProxyProtocolV1Connection(EndPoint endp, Connector connector, String next, ByteBuffer buffer) { super(endp, connector.getExecutor()); _connector = connector; _next = next; _length = buffer.remaining(); parse(buffer); } @Override public void onOpen() { super.onOpen(); fillInterested(); } private boolean parse(ByteBuffer buffer) { // parse fields while (buffer.hasRemaining()) { byte b = buffer.get(); if (_fields < 6) { if (b == ' ' || b == '\r' && _fields == 5) { _field[_fields++] = _builder.toString(); _builder.setLength(0); } else if (b < ' ') { LOG.warn("Bad character {} for {}", b & 0xFF, getEndPoint()); close(); return false; } else { _builder.append((char)b); } } else { if (b == '\n') { _fields = 7; return true; } LOG.warn("Bad CRLF for {}", getEndPoint()); close(); return false; } } return true; } @Override public void onFillable() { try { ByteBuffer buffer = null; while (_fields < 7) { // Create a buffer that will not read too much data // since once read it is impossible to push back for the // real connection to read it. int size = Math.max(1, SIZE[_fields] - _builder.length()); if (buffer == null || buffer.capacity() != size) buffer = BufferUtil.allocate(size); else BufferUtil.clear(buffer); // Read data int fill = getEndPoint().fill(buffer); if (fill < 0) { getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } _length += fill; if (_length >= 108) { LOG.warn("PROXY line too long {} for {}", _length, getEndPoint()); close(); return; } if (!parse(buffer)) return; } // Check proxy if (!"PROXY".equals(_field[0])) { LOG.warn("Not PROXY protocol for {}", getEndPoint()); close(); return; } // Extract Addresses InetSocketAddress remote = new InetSocketAddress(_field[2], Integer.parseInt(_field[4])); InetSocketAddress local = new InetSocketAddress(_field[3], Integer.parseInt(_field[5])); // Create the next protocol ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next); if (connectionFactory == null) { LOG.warn("No Next protocol '{}' for {}", _next, getEndPoint()); close(); return; } if (LOG.isDebugEnabled()) LOG.warn("Next protocol '{}' for {} r={} l={}", _next, getEndPoint(), remote, local); EndPoint endPoint = new ProxyEndPoint(getEndPoint(), remote, local); Connection newConnection = connectionFactory.newConnection(_connector, endPoint); endPoint.upgrade(newConnection); } catch (Throwable x) { LOG.warn("PROXY error for " + getEndPoint(), x); close(); } } } private enum Family { UNSPEC, INET, INET6, UNIX } private enum Transport { UNSPEC, STREAM, DGRAM } private static final byte[] MAGIC = new byte[]{0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A}; public class ProxyProtocolV2Connection extends AbstractConnection { private final Connector _connector; private final String _next; private final boolean _local; private final Family _family; private final Transport _transport; private final int _length; private final ByteBuffer _buffer; protected ProxyProtocolV2Connection(EndPoint endp, Connector connector, String next, ByteBuffer buffer) throws IOException { super(endp, connector.getExecutor()); _connector = connector; _next = next; if (buffer.remaining() != 16) throw new IllegalStateException(); if (LOG.isDebugEnabled()) LOG.debug("PROXYv2 header {} for {}", BufferUtil.toHexSummary(buffer), this); // struct proxy_hdr_v2 { // uint8_t sig[12]; /* hex 0D 0A 0D 0A 00 0D 0A 51 55 49 54 0A */ // uint8_t ver_cmd; /* protocol version and command */ // uint8_t fam; /* protocol family and address */ // uint16_t len; /* number of following bytes part of the header */ // }; for (byte magic : MAGIC) { if (buffer.get() != magic) throw new IOException("Bad PROXY protocol v2 signature"); } int versionAndCommand = 0xff & buffer.get(); if ((versionAndCommand & 0xf0) != 0x20) throw new IOException("Bad PROXY protocol v2 version"); _local = (versionAndCommand & 0xf) == 0x00; int transportAndFamily = 0xff & buffer.get(); switch (transportAndFamily >> 4) { case 0: _family = Family.UNSPEC; break; case 1: _family = Family.INET; break; case 2: _family = Family.INET6; break; case 3: _family = Family.UNIX; break; default: throw new IOException("Bad PROXY protocol v2 family"); } switch (0xf & transportAndFamily) { case 0: _transport = Transport.UNSPEC; break; case 1: _transport = Transport.STREAM; break; case 2: _transport = Transport.DGRAM; break; default: throw new IOException("Bad PROXY protocol v2 family"); } _length = buffer.getChar(); if (!_local && (_family == Family.UNSPEC || _family == Family.UNIX || _transport != Transport.STREAM)) throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x", versionAndCommand, transportAndFamily)); if (_length > getMaxProxyHeader()) throw new IOException(String.format("Unsupported PROXY protocol v2 mode 0x%x,0x%x,0x%x", versionAndCommand, transportAndFamily, _length)); _buffer = _length > 0 ? BufferUtil.allocate(_length) : BufferUtil.EMPTY_BUFFER; } @Override public void onOpen() { super.onOpen(); if (_buffer.remaining() == _length) next(); else fillInterested(); } @Override public void onFillable() { try { while (_buffer.remaining() < _length) { // Read data int fill = getEndPoint().fill(_buffer); if (fill < 0) { getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } } next(); } catch (Throwable x) { LOG.warn("PROXY error for " + getEndPoint(), x); close(); } } private void next() { if (LOG.isDebugEnabled()) LOG.debug("PROXYv2 next {} from {} for {}", _next, BufferUtil.toHexSummary(_buffer), this); // Create the next protocol ConnectionFactory connectionFactory = _connector.getConnectionFactory(_next); if (connectionFactory == null) { LOG.info("Next protocol '{}' for {}", _next, getEndPoint()); close(); return; } // Do we need to wrap the endpoint? EndPoint endPoint = getEndPoint(); if (!_local) { try { InetAddress src; InetAddress dst; int sp; int dp; switch (_family) { case INET: { byte[] addr = new byte[4]; _buffer.get(addr); src = Inet4Address.getByAddress(addr); _buffer.get(addr); dst = Inet4Address.getByAddress(addr); sp = _buffer.getChar(); dp = _buffer.getChar(); break; } case INET6: { byte[] addr = new byte[16]; _buffer.get(addr); src = Inet6Address.getByAddress(addr); _buffer.get(addr); dst = Inet6Address.getByAddress(addr); sp = _buffer.getChar(); dp = _buffer.getChar(); break; } default: throw new IllegalStateException(); } // Extract Addresses InetSocketAddress remote = new InetSocketAddress(src, sp); InetSocketAddress local = new InetSocketAddress(dst, dp); ProxyEndPoint proxyEndPoint = new ProxyEndPoint(endPoint, remote, local); endPoint = proxyEndPoint; // Any additional info? while (_buffer.hasRemaining()) { int type = 0xff & _buffer.get(); int length = _buffer.getShort(); byte[] value = new byte[length]; _buffer.get(value); if (LOG.isDebugEnabled()) LOG.debug(String.format("T=%x L=%d V=%s for %s", type, length, TypeUtil.toHexString(value), this)); switch (type) { case 0x20: // PP2_TYPE_SSL { int client = value[0] & 0xFF; switch (client) { case 0x01: // PP2_CLIENT_SSL { int i = 5; // Index of the first sub_tlv, after verify. while (i < length) { int subType = value[i++] & 0xFF; int subLength = (value[i++] & 0xFF) * 256 + (value[i++] & 0xFF); byte[] subValue = new byte[subLength]; System.arraycopy(value, i, subValue, 0, subLength); i += subLength; switch (subType) { case 0x21: // PP2_SUBTYPE_SSL_VERSION String tlsVersion = new String(subValue, StandardCharsets.US_ASCII); proxyEndPoint.setAttribute(TLS_VERSION, tlsVersion); break; case 0x22: // PP2_SUBTYPE_SSL_CN case 0x23: // PP2_SUBTYPE_SSL_CIPHER case 0x24: // PP2_SUBTYPE_SSL_SIG_ALG case 0x25: // PP2_SUBTYPE_SSL_KEY_ALG default: break; } } break; } case 0x02: // PP2_CLIENT_CERT_CONN case 0x04: // PP2_CLIENT_CERT_SESS default: break; } break; } case 0x01: // PP2_TYPE_ALPN case 0x02: // PP2_TYPE_AUTHORITY case 0x03: // PP2_TYPE_CRC32C case 0x04: // PP2_TYPE_NOOP case 0x30: // PP2_TYPE_NETNS default: break; } } if (LOG.isDebugEnabled()) LOG.debug("{} {}", getEndPoint(), proxyEndPoint.toString()); } catch (Exception e) { LOG.warn(e); } } Connection newConnection = connectionFactory.newConnection(_connector, endPoint); endPoint.upgrade(newConnection); } } public static class ProxyEndPoint extends AttributesMap implements EndPoint { private final EndPoint _endp; private final InetSocketAddress _remote; private final InetSocketAddress _local; public ProxyEndPoint(EndPoint endp, InetSocketAddress remote, InetSocketAddress local) { _endp = endp; _remote = remote; _local = local; } @Override public boolean isOptimizedForDirectBuffers() { return _endp.isOptimizedForDirectBuffers(); } @Override public InetSocketAddress getLocalAddress() { return _local; } @Override public InetSocketAddress getRemoteAddress() { return _remote; } @Override public String toString() { return String.format("%s@%x[remote=%s,local=%s,endpoint=%s]", getClass().getSimpleName(), hashCode(), _remote, _local, _endp); } @Override public boolean isOpen() { return _endp.isOpen(); } @Override public long getCreatedTimeStamp() { return _endp.getCreatedTimeStamp(); } @Override public void shutdownOutput() { _endp.shutdownOutput(); } @Override public boolean isOutputShutdown() { return _endp.isOutputShutdown(); } @Override public boolean isInputShutdown() { return _endp.isInputShutdown(); } @Override public void close(Throwable cause) { _endp.close(cause); } @Override public int fill(ByteBuffer buffer) throws IOException { return _endp.fill(buffer); } @Override public boolean flush(ByteBuffer... buffer) throws IOException { return _endp.flush(buffer); } @Override public Object getTransport() { return _endp.getTransport(); } @Override public long getIdleTimeout() { return _endp.getIdleTimeout(); } @Override public void setIdleTimeout(long idleTimeout) { _endp.setIdleTimeout(idleTimeout); } @Override public void fillInterested(Callback callback) throws ReadPendingException { _endp.fillInterested(callback); } @Override public boolean tryFillInterested(Callback callback) { return _endp.tryFillInterested(callback); } @Override public boolean isFillInterested() { return _endp.isFillInterested(); } @Override public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException { _endp.write(callback, buffers); } @Override public Connection getConnection() { return _endp.getConnection(); } @Override public void setConnection(Connection connection) { _endp.setConnection(connection); } @Override public void onOpen() { _endp.onOpen(); } @Override public void onClose(Throwable cause) { _endp.onClose(cause); } @Override public void upgrade(Connection newConnection) { _endp.upgrade(newConnection); } } }