All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.server.ProxyConnectionFactory Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
// 
// ========================================================================
// Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
// ------------------------------------------------------------------------
// 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 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 http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt * * @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. */ @Deprecated(since = "2021-05-27") public class ProxyConnectionFactory extends DetectorConnectionFactory { public static final String TLS_VERSION = "TLS_VERSION"; private static final Logger LOG = Log.getLogger(ProxyConnectionFactory.class); public ProxyConnectionFactory() { this(null); } public ProxyConnectionFactory(String nextProtocol) { super(new ProxyV1ConnectionFactory(nextProtocol), new ProxyV2ConnectionFactory(nextProtocol)); } private static ConnectionFactory findNextConnectionFactory(String nextProtocol, Connector connector, String currentProtocol, EndPoint endp) { currentProtocol = "[" + currentProtocol + "]"; if (LOG.isDebugEnabled()) LOG.debug("finding connection factory following {} for protocol {}", currentProtocol, nextProtocol); String nextProtocolToFind = nextProtocol; if (nextProtocol == null) nextProtocolToFind = AbstractConnectionFactory.findNextProtocol(connector, currentProtocol); if (nextProtocolToFind == null) throw new IllegalStateException("Cannot find protocol following '" + currentProtocol + "' in connector's protocol list " + connector.getProtocols() + " for " + endp); ConnectionFactory connectionFactory = connector.getConnectionFactory(nextProtocolToFind); if (connectionFactory == null) throw new IllegalStateException("Cannot find protocol '" + nextProtocol + "' in connector's protocol list " + connector.getProtocols() + " for " + endp); if (LOG.isDebugEnabled()) LOG.debug("found next connection factory {} for protocol {}", connectionFactory, nextProtocol); return connectionFactory; } public int getMaxProxyHeader() { ProxyV2ConnectionFactory v2 = getBean(ProxyV2ConnectionFactory.class); return v2.getMaxProxyHeader(); } public void setMaxProxyHeader(int maxProxyHeader) { ProxyV2ConnectionFactory v2 = getBean(ProxyV2ConnectionFactory.class); v2.setMaxProxyHeader(maxProxyHeader); } // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") private static class ProxyV1ConnectionFactory extends AbstractConnectionFactory implements Detecting { private static final byte[] SIGNATURE = "PROXY".getBytes(StandardCharsets.US_ASCII); private final String _nextProtocol; private ProxyV1ConnectionFactory(String nextProtocol) { super("proxy"); this._nextProtocol = nextProtocol; } @Override public Detection detect(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 attempting detection with {} bytes", buffer.remaining()); if (buffer.remaining() < SIGNATURE.length) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 detection requires more bytes"); return Detection.NEED_MORE_BYTES; } for (int i = 0; i < SIGNATURE.length; i++) { byte signatureByte = SIGNATURE[i]; byte byteInBuffer = buffer.get(i); if (byteInBuffer != signatureByte) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 detection unsuccessful"); return Detection.NOT_RECOGNIZED; } } if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 detection succeeded"); return Detection.RECOGNIZED; } @Override public Connection newConnection(Connector connector, EndPoint endp) { ConnectionFactory nextConnectionFactory = findNextConnectionFactory(_nextProtocol, connector, getProtocol(), endp); return configure(new ProxyProtocolV1Connection(endp, connector, nextConnectionFactory), connector, endp); } // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") private static class ProxyProtocolV1Connection extends AbstractConnection implements Connection.UpgradeFrom, Connection.UpgradeTo { // 0 1 2 3 4 5 6 // 98765432109876543210987654321 // PROXY P R.R.R.R L.L.L.L R Lrn private static final int CR_INDEX = 6; private static final int LF_INDEX = 7; private final Connector _connector; private final ConnectionFactory _next; private final ByteBuffer _buffer; private final StringBuilder _builder = new StringBuilder(); private final String[] _fields = new String[6]; private int _index; private int _length; private ProxyProtocolV1Connection(EndPoint endp, Connector connector, ConnectionFactory next) { super(endp, connector.getExecutor()); _connector = connector; _next = next; _buffer = _connector.getByteBufferPool().acquire(getInputBufferSize(), true); } @Override public void onFillable() { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 onFillable current index = ", _index); try { while (_index < LF_INDEX) { // Read data int fill = getEndPoint().fill(_buffer); if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 filled buffer with {} bytes", fill); if (fill < 0) { _connector.getByteBufferPool().release(_buffer); getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } if (parse()) break; } if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 onFillable parsing done, now upgrading"); upgrade(); } catch (Throwable x) { LOG.warn("Proxy v1 error for {}", getEndPoint(), x); releaseAndClose(); } } @Override public void onOpen() { super.onOpen(); try { while (_index < LF_INDEX) { if (!parse()) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 onOpen parsing ran out of bytes, marking as fillInterested"); fillInterested(); return; } } if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 onOpen parsing done, now upgrading"); upgrade(); } catch (Throwable x) { LOG.warn("Proxy v1 error for {}", getEndPoint(), x); releaseAndClose(); } } @Override public ByteBuffer onUpgradeFrom() { if (_buffer.hasRemaining()) { ByteBuffer unconsumed = ByteBuffer.allocateDirect(_buffer.remaining()); unconsumed.put(_buffer); unconsumed.flip(); _connector.getByteBufferPool().release(_buffer); return unconsumed; } return null; } @Override public void onUpgradeTo(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 copying unconsumed buffer {}", BufferUtil.toDetailString(buffer)); BufferUtil.append(_buffer, buffer); } /** * @return true when parsing is done, false when more bytes are needed. */ private boolean parse() throws IOException { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 parsing {}", BufferUtil.toDetailString(_buffer)); _length += _buffer.remaining(); // Parse fields while (_buffer.hasRemaining()) { byte b = _buffer.get(); if (_index < CR_INDEX) { if (b == ' ' || b == '\r') { _fields[_index++] = _builder.toString(); _builder.setLength(0); if (b == '\r') _index = CR_INDEX; } else if (b < ' ') { throw new IOException("Proxy v1 bad character " + (b & 0xFF)); } else { _builder.append((char) b); } } else { if (b == '\n') { _index = LF_INDEX; if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 parsing is done"); return true; } throw new IOException("Proxy v1 bad CRLF " + (b & 0xFF)); } } // Not enough bytes. if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 parsing requires more bytes"); return false; } private void releaseAndClose() { if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 releasing buffer and closing"); _connector.getByteBufferPool().release(_buffer); close(); } private void upgrade() { int proxyLineLength = _length - _buffer.remaining(); if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 pre-upgrade packet length (including CRLF) is {}", proxyLineLength); if (proxyLineLength >= 110) { LOG.warn("Proxy v1 PROXY line too long {} for {}", proxyLineLength, getEndPoint()); releaseAndClose(); return; } // Check proxy if (!"PROXY".equals(_fields[0])) { LOG.warn("Proxy v1 not PROXY protocol for {}", getEndPoint()); releaseAndClose(); return; } String srcIP = _fields[2]; String srcPort = _fields[4]; String dstIP = _fields[3]; String dstPort = _fields[5]; // If UNKNOWN, we must ignore the information sent, so use the EndPoint's. boolean unknown = "UNKNOWN".equalsIgnoreCase(_fields[1]); if (unknown) { srcIP = getEndPoint().getRemoteAddress().getAddress().getHostAddress(); srcPort = String.valueOf(getEndPoint().getRemoteAddress().getPort()); dstIP = getEndPoint().getLocalAddress().getAddress().getHostAddress(); dstPort = String.valueOf(getEndPoint().getLocalAddress().getPort()); } InetSocketAddress remote = new InetSocketAddress(srcIP, Integer.parseInt(srcPort)); InetSocketAddress local = new InetSocketAddress(dstIP, Integer.parseInt(dstPort)); if (LOG.isDebugEnabled()) LOG.debug("Proxy v1 next protocol '{}' for {} r={} l={}", _next, getEndPoint(), remote, local); EndPoint endPoint = new ProxyEndPoint(getEndPoint(), remote, local); upgradeToConnectionFactory(_next, _connector, endPoint); } } } // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") private static class ProxyV2ConnectionFactory extends AbstractConnectionFactory implements Detecting { private enum Family { UNSPEC, INET, INET6, UNIX } private enum Transport { UNSPEC, STREAM, DGRAM } private static final byte[] SIGNATURE = new byte[] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A }; private final String _nextProtocol; private int _maxProxyHeader = 1024; private ProxyV2ConnectionFactory(String nextProtocol) { super("proxy"); this._nextProtocol = nextProtocol; } @Override public Detection detect(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 attempting detection with {} bytes", buffer.remaining()); if (buffer.remaining() < SIGNATURE.length) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 detection requires more bytes"); return Detection.NEED_MORE_BYTES; } for (int i = 0; i < SIGNATURE.length; i++) { byte signatureByte = SIGNATURE[i]; byte byteInBuffer = buffer.get(i); if (byteInBuffer != signatureByte) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 detection unsuccessful"); return Detection.NOT_RECOGNIZED; } } if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 detection succeeded"); return Detection.RECOGNIZED; } public int getMaxProxyHeader() { return _maxProxyHeader; } public void setMaxProxyHeader(int maxProxyHeader) { _maxProxyHeader = maxProxyHeader; } @Override public Connection newConnection(Connector connector, EndPoint endp) { ConnectionFactory nextConnectionFactory = findNextConnectionFactory(_nextProtocol, connector, getProtocol(), endp); return configure(new ProxyProtocolV2Connection(endp, connector, nextConnectionFactory), connector, endp); } // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") private class ProxyProtocolV2Connection extends AbstractConnection implements Connection.UpgradeFrom, Connection.UpgradeTo { private static final int HEADER_LENGTH = 16; private final Connector _connector; private final ConnectionFactory _next; private final ByteBuffer _buffer; private boolean _local; private Family _family; private int _length; private boolean _headerParsed; protected ProxyProtocolV2Connection(EndPoint endp, Connector connector, ConnectionFactory next) { super(endp, connector.getExecutor()); _connector = connector; _next = next; _buffer = _connector.getByteBufferPool().acquire(getInputBufferSize(), true); } @Override public void onUpgradeTo(ByteBuffer buffer) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 copying unconsumed buffer {}", BufferUtil.toDetailString(buffer)); BufferUtil.append(_buffer, buffer); } @Override public void onOpen() { super.onOpen(); try { parseHeader(); if (_headerParsed && _buffer.remaining() >= _length) { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 onOpen parsing fixed length packet part done, now upgrading"); parseBodyAndUpgrade(); } else { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 onOpen parsing fixed length packet ran out of bytes, marking as fillInterested"); fillInterested(); } } catch (Exception x) { LOG.warn("Proxy v2 error for {}", getEndPoint(), x); releaseAndClose(); } } @Override public void onFillable() { try { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 onFillable header parsed? ", _headerParsed); while (!_headerParsed) { // Read data int fill = getEndPoint().fill(_buffer); if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 filled buffer with {} bytes", fill); if (fill < 0) { _connector.getByteBufferPool().release(_buffer); getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } parseHeader(); } if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 onFillable header parsed, length = {}, buffer = {}", _length, BufferUtil.toDetailString(_buffer)); while (_buffer.remaining() < _length) { // Read data int fill = getEndPoint().fill(_buffer); if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 filled buffer with {} bytes", fill); if (fill < 0) { _connector.getByteBufferPool().release(_buffer); getEndPoint().shutdownOutput(); return; } if (fill == 0) { fillInterested(); return; } } parseBodyAndUpgrade(); } catch (Throwable x) { LOG.warn("Proxy v2 error for " + getEndPoint(), x); releaseAndClose(); } } @Override public ByteBuffer onUpgradeFrom() { if (_buffer.hasRemaining()) { ByteBuffer unconsumed = ByteBuffer.allocateDirect(_buffer.remaining()); unconsumed.put(_buffer); unconsumed.flip(); _connector.getByteBufferPool().release(_buffer); return unconsumed; } return null; } private void parseBodyAndUpgrade() throws IOException { // stop reading when bufferRemainingReserve bytes are remaining in the buffer int nonProxyRemaining = _buffer.remaining() - _length; if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 parsing body, length = {}, buffer = {}", _length, BufferUtil.toHexSummary(_buffer)); if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 body {} from {} for {}", _next, BufferUtil.toHexSummary(_buffer), this); // Do we need to wrap the endpoint? EndPoint endPoint = getEndPoint(); if (!_local) { 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.remaining() > nonProxyRemaining) { int type = 0xff & _buffer.get(); int length = _buffer.getChar(); byte[] value = new byte[length]; _buffer.get(value); if (LOG.isDebugEnabled()) LOG.debug(String.format("Proxy v2 T=%x L=%d V=%s for %s", type, length, TypeUtil.toHexString(value), this)); switch(type) { case // PP2_TYPE_SSL 0x20: { int client = value[0] & 0xFF; switch(client) { case // PP2_CLIENT_SSL 0x01: { // Index of the first sub_tlv, after verify. int i = 5; 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 // PP2_SUBTYPE_SSL_VERSION 0x21: String tlsVersion = new String(subValue, StandardCharsets.US_ASCII); proxyEndPoint.setAttribute(TLS_VERSION, tlsVersion); break; // PP2_SUBTYPE_SSL_CN case 0x22: // PP2_SUBTYPE_SSL_CIPHER case 0x23: // PP2_SUBTYPE_SSL_SIG_ALG case 0x24: // PP2_SUBTYPE_SSL_KEY_ALG case 0x25: default: break; } } break; } // PP2_CLIENT_CERT_CONN case 0x02: // PP2_CLIENT_CERT_SESS case 0x04: default: break; } break; } // PP2_TYPE_ALPN case 0x01: // PP2_TYPE_AUTHORITY case 0x02: // PP2_TYPE_CRC32C case 0x03: // PP2_TYPE_NOOP case 0x04: // PP2_TYPE_NETNS case 0x30: default: break; } } if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 {} {}", getEndPoint(), proxyEndPoint.toString()); } else { _buffer.position(_buffer.position() + _length); } if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 parsing dynamic packet part is now done, upgrading to {}", _nextProtocol); upgradeToConnectionFactory(_next, _connector, endPoint); } private void parseHeader() throws IOException { if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 parsing fixed length packet part, buffer = {}", BufferUtil.toDetailString(_buffer)); if (_buffer.remaining() < HEADER_LENGTH) return; if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 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 signatureByte : SIGNATURE) { if (_buffer.get() != signatureByte) throw new IOException("Proxy v2 bad PROXY signature"); } int versionAndCommand = 0xFF & _buffer.get(); if ((versionAndCommand & 0xF0) != 0x20) throw new IOException("Proxy v2 bad PROXY 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("Proxy v2 bad PROXY family"); } Transport transport; 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("Proxy v2 bad PROXY family"); } _length = _buffer.getChar(); if (!_local && (_family == Family.UNSPEC || _family == Family.UNIX || transport != Transport.STREAM)) throw new IOException(String.format("Proxy v2 unsupported PROXY mode 0x%x,0x%x", versionAndCommand, transportAndFamily)); if (_length > getMaxProxyHeader()) throw new IOException(String.format("Proxy v2 Unsupported PROXY mode 0x%x,0x%x,0x%x", versionAndCommand, transportAndFamily, _length)); if (LOG.isDebugEnabled()) LOG.debug("Proxy v2 fixed length packet part is now parsed"); _headerParsed = true; } private void releaseAndClose() { _connector.getByteBufferPool().release(_buffer); close(); } } } // @deprecated The Eclipse Jetty and Apache Felix Http Jetty packages are no longer supported. @Deprecated(since = "2021-05-27") 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; } public EndPoint unwrap() { return _endp; } @Override public void close() { _endp.close(); } @Override public int fill(ByteBuffer buffer) throws IOException { return _endp.fill(buffer); } @Override public void fillInterested(Callback callback) throws ReadPendingException { _endp.fillInterested(callback); } @Override public boolean flush(ByteBuffer... buffer) throws IOException { return _endp.flush(buffer); } @Override public Connection getConnection() { return _endp.getConnection(); } @Override public void setConnection(Connection connection) { _endp.setConnection(connection); } @Override public long getCreatedTimeStamp() { return _endp.getCreatedTimeStamp(); } @Override public long getIdleTimeout() { return _endp.getIdleTimeout(); } @Override public void setIdleTimeout(long idleTimeout) { _endp.setIdleTimeout(idleTimeout); } @Override public InetSocketAddress getLocalAddress() { return _local; } @Override public InetSocketAddress getRemoteAddress() { return _remote; } @Override public Object getTransport() { return _endp.getTransport(); } @Override public boolean isFillInterested() { return _endp.isFillInterested(); } @Override public boolean isInputShutdown() { return _endp.isInputShutdown(); } @Override public boolean isOpen() { return _endp.isOpen(); } @Override public boolean isOptimizedForDirectBuffers() { return _endp.isOptimizedForDirectBuffers(); } @Override public boolean isOutputShutdown() { return _endp.isOutputShutdown(); } @Override public void onClose() { _endp.onClose(); } @Override public void onOpen() { _endp.onOpen(); } @Override public void shutdownOutput() { _endp.shutdownOutput(); } @Override public String toString() { return String.format("%s@%x[remote=%s,local=%s,endpoint=%s]", getClass().getSimpleName(), hashCode(), _remote, _local, _endp); } @Override public boolean tryFillInterested(Callback callback) { return _endp.tryFillInterested(callback); } @Override public void upgrade(Connection newConnection) { _endp.upgrade(newConnection); } @Override public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException { _endp.write(callback, buffers); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy