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

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

//
//  ========================================================================
//  Copyright (c) 1995-2018 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 http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt */ 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 */ 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 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 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 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() { _endp.close(); } @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() { _endp.onClose(); } @Override public void upgrade(Connection newConnection) { _endp.upgrade(newConnection); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy