From aa684a5dccd76d765b1e2376af3f76291c8b1333 Mon Sep 17 00:00:00 2001 From: Simone Bordet Date: Tue, 11 Aug 2015 12:17:24 +0200 Subject: 470311 - Introduce a proxy-protocol module. Support for the PROXY protocol is now enabled via 2 new modules: proxy-protocol and proxy-protocol-ssl, respectively for the HTTP connector and the SSL connector. --- jetty-server/src/main/config/etc/jetty-http.xml | 4 -- .../main/config/etc/jetty-proxy-protocol-ssl.xml | 10 ++++ .../src/main/config/etc/jetty-proxy-protocol.xml | 10 ++++ .../src/main/config/modules/proxy-protocol-ssl.mod | 9 +++ .../src/main/config/modules/proxy-protocol.mod | 9 +++ .../eclipse/jetty/server/AbstractConnector.java | 41 ++++++++----- .../jetty/server/ProxyConnectionFactory.java | 69 ++++++++++++---------- .../eclipse/jetty/server/ServerConnectorTest.java | 48 ++++++++++----- 8 files changed, 136 insertions(+), 64 deletions(-) create mode 100644 jetty-server/src/main/config/etc/jetty-proxy-protocol-ssl.xml create mode 100644 jetty-server/src/main/config/etc/jetty-proxy-protocol.xml create mode 100644 jetty-server/src/main/config/modules/proxy-protocol-ssl.mod create mode 100644 jetty-server/src/main/config/modules/proxy-protocol.mod (limited to 'jetty-server/src') diff --git a/jetty-server/src/main/config/etc/jetty-http.xml b/jetty-server/src/main/config/etc/jetty-http.xml index 0cbc5a0871..ebeed5d1fc 100644 --- a/jetty-server/src/main/config/etc/jetty-http.xml +++ b/jetty-server/src/main/config/etc/jetty-http.xml @@ -26,10 +26,6 @@ - diff --git a/jetty-server/src/main/config/etc/jetty-proxy-protocol-ssl.xml b/jetty-server/src/main/config/etc/jetty-proxy-protocol-ssl.xml new file mode 100644 index 0000000000..91452f2738 --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-proxy-protocol-ssl.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/jetty-server/src/main/config/etc/jetty-proxy-protocol.xml b/jetty-server/src/main/config/etc/jetty-proxy-protocol.xml new file mode 100644 index 0000000000..5169c4fddd --- /dev/null +++ b/jetty-server/src/main/config/etc/jetty-proxy-protocol.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod new file mode 100644 index 0000000000..764d24b847 --- /dev/null +++ b/jetty-server/src/main/config/modules/proxy-protocol-ssl.mod @@ -0,0 +1,9 @@ +# +# PROXY Protocol Module - SSL +# + +[depend] +ssl + +[xml] +etc/jetty-proxy-protocol-ssl.xml diff --git a/jetty-server/src/main/config/modules/proxy-protocol.mod b/jetty-server/src/main/config/modules/proxy-protocol.mod new file mode 100644 index 0000000000..9df2700f4e --- /dev/null +++ b/jetty-server/src/main/config/modules/proxy-protocol.mod @@ -0,0 +1,9 @@ +# +# PROXY Protocol Module - HTTP +# + +[depend] +http + +[xml] +etc/jetty-proxy-protocol.xml diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java index f13aa1ad7e..bfd8206df9 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/AbstractConnector.java @@ -144,7 +144,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co private final Scheduler _scheduler; private final ByteBufferPool _byteBufferPool; private final Thread[] _acceptors; - private final Set _endpoints = Collections.newSetFromMap(new ConcurrentHashMap()); + private final Set _endpoints = Collections.newSetFromMap(new ConcurrentHashMap<>()); private final Set _immutableEndPoints = Collections.unmodifiableSet(_endpoints); private volatile CountDownLatch _stopping; private long _idleTimeout = 30000; @@ -191,7 +191,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co int cores = Runtime.getRuntime().availableProcessors(); if (acceptors < 0) - acceptors=Math.max(1, Math.min(4,cores/8)); + acceptors=Math.max(1, Math.min(4,cores/8)); if (acceptors > cores) LOG.warn("Acceptors should be <= availableProcessors: " + this); _acceptors = new Thread[acceptors]; @@ -303,7 +303,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co _stopping=null; super.doStop(); - + for (Acceptor a : getBeans(Acceptor.class)) removeBean(a); @@ -362,7 +362,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co { synchronized (_factories) { - Set to_remove = new HashSet(); + Set to_remove = new HashSet<>(); for (String key:factory.getProtocols()) { key=StringUtil.asciiToLowerCase(key); @@ -375,11 +375,11 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co } _factories.put(key, factory); } - + // keep factories still referenced for (ConnectionFactory f : _factories.values()) to_remove.remove(f); - + // remove old factories for (ConnectionFactory old: to_remove) { @@ -396,7 +396,20 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co LOG.debug("{} added {}", this, factory); } } - + + public void addFirstConnectionFactory(ConnectionFactory factory) + { + synchronized (_factories) + { + List existings = new ArrayList<>(_factories.values()); + _factories.clear(); + addConnectionFactory(factory); + for (ConnectionFactory existing : existings) + addConnectionFactory(existing); + _defaultProtocol = factory.getProtocol(); + } + } + public void addIfAbsentConnectionFactory(ConnectionFactory factory) { synchronized (_factories) @@ -460,8 +473,8 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co /* ------------------------------------------------------------ */ /** Set the acceptor thread priority delta. *

This allows the acceptor thread to run at a different priority. - * Typically this would be used to lower the priority to give preference - * to handling previously accepted connections rather than accepting + * Typically this would be used to lower the priority to give preference + * to handling previously accepted connections rather than accepting * new connections

* @param acceptorPriorityDelta the acceptor priority delta */ @@ -532,7 +545,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co String name=thread.getName(); _name=String.format("%s-acceptor-%d@%x-%s",name,_id,hashCode(),AbstractConnector.this.toString()); thread.setName(_name); - + int priority=thread.getPriority(); if (_acceptorPriorityDelta!=0) thread.setPriority(Math.max(Thread.MIN_PRIORITY,Math.min(Thread.MAX_PRIORITY,priority+_acceptorPriorityDelta))); @@ -574,7 +587,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co stopping.countDown(); } } - + @Override public String toString() { @@ -583,7 +596,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co return String.format("acceptor-%d@%x", _id, hashCode()); return name; } - + } @@ -636,7 +649,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co { return _name; } - + /* ------------------------------------------------------------ */ /** * Set a connector name. A context may be configured with @@ -648,7 +661,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co { _name=name; } - + @Override public String toString() { diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java index f810f4c55a..b72cf5718d 100644 --- a/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/ProxyConnectionFactory.java @@ -35,11 +35,11 @@ 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 line before the normal protocol handling

- * + * * @see http://www.haproxy.org/download/1.5/doc/proxy-protocol.txt */ public class ProxyConnectionFactory extends AbstractConnectionFactory @@ -48,7 +48,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory private final String _next; /* ------------------------------------------------------------ */ - /** Proxy Connection Factory that uses the next ConnectionFactory + /** Proxy Connection Factory that uses the next ConnectionFactory * on the connector as the next protocol */ public ProxyConnectionFactory() @@ -56,13 +56,13 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory super("proxy"); _next=null; } - + public ProxyConnectionFactory(String nextProtocol) { super("proxy"); _next=nextProtocol; } - + @Override public Connection newConnection(Connector connector, EndPoint endp) { @@ -71,7 +71,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory { for (Iterator i = connector.getProtocols().iterator();i.hasNext();) { - String p=i.next(); + String p=i.next(); if (getProtocol().equalsIgnoreCase(p)) { next=i.next(); @@ -79,16 +79,16 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory } } } - + return new ProxyConnection(endp,connector,next); } - + public static class ProxyConnection 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; @@ -96,7 +96,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory private final String[] _field=new String[6]; private int _fields; private int _length; - + protected ProxyConnection(EndPoint endp, Connector connector, String next) { super(endp,connector.getExecutor()); @@ -110,9 +110,9 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory super.onOpen(); fillInterested(); } - + @Override - public void onFillable() + public void onFillable() { try { @@ -125,7 +125,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory buffer=BufferUtil.allocate(size); else BufferUtil.clear(buffer); - + // Read data int fill=getEndPoint().fill(buffer); if (fill<0) @@ -138,15 +138,15 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory fillInterested(); return; } - + _length+=fill; if (_length>=108) { - LOG.warn("PROXY line too long {}",getEndPoint()); + LOG.warn("PROXY line too long {} for {}",_length,getEndPoint()); close(); return; } - + // parse fields while (buffer.hasRemaining()) { @@ -160,61 +160,60 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory } else if (b<' ') { - LOG.warn("Bad char {}",getEndPoint()); + LOG.warn("Bad character {} for {}",b&0xFF,getEndPoint()); close(); return; } else + { _builder.append((char)b); + } } else { if (b=='\n') break loop; - LOG.warn("Bad CRLF {}",getEndPoint()); + LOG.warn("Bad CRLF for {}",getEndPoint()); close(); return; - } } } - + // Check proxy if (!"PROXY".equals(_field[0])) { - LOG.warn("Bad PROXY {}",getEndPoint()); + LOG.warn("Not PROXY protocol for {}",getEndPoint()); close(); return; } - - // Extract Addresses + + // 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.info("{} next protocol '{}'",getEndPoint(), _next); + LOG.info("Next protocol '{}' for {}",_next,getEndPoint()); close(); return; } EndPoint endPoint = new ProxyEndPoint(getEndPoint(),remote,local); - Connection newConnection = connectionFactory.newConnection(_connector, endPoint); + Connection newConnection = connectionFactory.newConnection(_connector, endPoint); endPoint.upgrade(newConnection); } - catch (Throwable e) + catch (Throwable x) { - LOG.warn("Bad PROXY {} {}",e.toString(),getEndPoint()); - LOG.debug(e); + LOG.warn("PROXY error for "+getEndPoint(),x); close(); } } } - - + public static class ProxyEndPoint implements EndPoint { private final EndPoint _endp; @@ -233,7 +232,7 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory { return _endp.isOptimizedForDirectBuffers(); } - + public InetSocketAddress getLocalAddress() { return _local; @@ -304,6 +303,12 @@ public class ProxyConnectionFactory extends AbstractConnectionFactory _endp.fillInterested(callback); } + @Override + public boolean isFillInterested() + { + return _endp.isFillInterested(); + } + public void write(Callback callback, ByteBuffer... buffers) throws WritePendingException { _endp.write(callback,buffers); diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java index 8fc2a59106..c9432d880a 100644 --- a/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/ServerConnectorTest.java @@ -18,23 +18,16 @@ package org.eclipse.jetty.server; -import static org.hamcrest.Matchers.anyOf; -import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; -import static org.junit.Assert.assertThat; - import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; import java.lang.reflect.Field; import java.net.HttpURLConnection; -import java.net.MalformedURLException; import java.net.Socket; import java.net.URI; import java.net.URISyntaxException; import java.nio.charset.StandardCharsets; +import java.util.Collection; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -49,6 +42,14 @@ import org.eclipse.jetty.toolchain.test.OS; import org.eclipse.jetty.util.IO; import org.junit.Test; +import static org.hamcrest.Matchers.anyOf; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.instanceOf; +import static org.hamcrest.Matchers.notNullValue; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertThat; + public class ServerConnectorTest { public static class ReuseInfoHandler extends AbstractHandler @@ -79,9 +80,9 @@ public class ServerConnectorTest { t.printStackTrace(out); } - + out.printf("socket.getReuseAddress() = %b%n",socket.getReuseAddress()); - + baseRequest.setHandled(true); } } @@ -97,7 +98,7 @@ public class ServerConnectorTest return new URI(String.format("http://%s:%d/",host,port)); } - private String getResponse(URI uri) throws MalformedURLException, IOException + private String getResponse(URI uri) throws IOException { HttpURLConnection http = (HttpURLConnection)uri.toURL().openConnection(); assertThat("Valid Response Code",http.getResponseCode(),anyOf(is(200),is(404))); @@ -130,7 +131,7 @@ public class ServerConnectorTest String response = getResponse(uri); assertThat("Response",response,containsString("connector.getReuseAddress() = true")); assertThat("Response",response,containsString("connector._reuseAddress() = true")); - + // Java on Windows is incapable of propagating reuse-address this to the opened socket. if (!OS.IS_WINDOWS) { @@ -166,7 +167,7 @@ public class ServerConnectorTest String response = getResponse(uri); assertThat("Response",response,containsString("connector.getReuseAddress() = true")); assertThat("Response",response,containsString("connector._reuseAddress() = true")); - + // Java on Windows is incapable of propagating reuse-address this to the opened socket. if (!OS.IS_WINDOWS) { @@ -202,7 +203,7 @@ public class ServerConnectorTest String response = getResponse(uri); assertThat("Response",response,containsString("connector.getReuseAddress() = false")); assertThat("Response",response,containsString("connector._reuseAddress() = false")); - + // Java on Windows is incapable of propagating reuse-address this to the opened socket. if (!OS.IS_WINDOWS) { @@ -214,4 +215,23 @@ public class ServerConnectorTest server.stop(); } } + + @Test + public void testAddFirstConnectionFactory() throws Exception + { + Server server = new Server(); + ServerConnector connector = new ServerConnector(server); + server.addConnector(connector); + + HttpConnectionFactory http = new HttpConnectionFactory(); + connector.addConnectionFactory(http); + ProxyConnectionFactory proxy = new ProxyConnectionFactory(); + connector.addFirstConnectionFactory(proxy); + + Collection factories = connector.getConnectionFactories(); + assertEquals(2, factories.size()); + assertSame(proxy, factories.iterator().next()); + assertEquals(2, connector.getBeans(ConnectionFactory.class).size()); + assertEquals(proxy.getProtocol(), connector.getDefaultProtocol()); + } } -- cgit v1.2.3