From e39c66d42568cc63e50d935ad7036bd87dd1bd18 Mon Sep 17 00:00:00 2001 From: Jan Bartel Date: Thu, 13 Jul 2017 16:03:58 +0200 Subject: [PATCH] Issue #1645 --- .../org/eclipse/jetty/servlets/DoSFilter.java | 131 +++++++++++++++--- .../jetty/servlets/AbstractDoSFilterTest.java | 21 +++ .../eclipse/jetty/servlets/DoSFilterTest.java | 9 +- 3 files changed, 140 insertions(+), 21 deletions(-) diff --git a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java index a5dab447b6b..9920479b944 100644 --- a/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java +++ b/jetty-servlets/src/main/java/org/eclipse/jetty/servlets/DoSFilter.java @@ -146,7 +146,8 @@ public class DoSFilter implements Filter private static final long __DEFAULT_THROTTLE_MS = 30000L; private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L; private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L; - + + static final String NAME = "name"; static final String MANAGED_ATTR_INIT_PARAM = "managedAttr"; static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec"; static final String DELAY_MS_INIT_PARAM = "delayMs"; @@ -181,12 +182,14 @@ public class DoSFilter implements Filter private volatile boolean _trackSessions; private volatile boolean _remotePort; private volatile boolean _enabled; + private volatile String _name; private Semaphore _passes; private volatile int _throttledRequests; private volatile int _maxRequestsPerSec; private Queue[] _queues; private AsyncListener[] _listeners; private Scheduler _scheduler; + private ServletContext _context; public void init(FilterConfig filterConfig) throws ServletException { @@ -263,11 +266,14 @@ public class DoSFilter implements Filter parameter = filterConfig.getInitParameter(TOO_MANY_CODE); setTooManyCode(parameter==null?429:Integer.parseInt(parameter)); - _scheduler = startScheduler(); + setName(filterConfig.getFilterName()); + _context = filterConfig.getServletContext(); + if (_context != null ) + { + _context.setAttribute(filterConfig.getFilterName(), this); + } - ServletContext context = filterConfig.getServletContext(); - if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM))) - context.setAttribute(filterConfig.getFilterName(), this); + _scheduler = startScheduler(); } protected Scheduler startScheduler() throws ServletException @@ -536,6 +542,11 @@ public class DoSFilter implements Filter { return USER_AUTH; } + + public void schedule (RateTracker tracker) + { + _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS); + } /** * Return a request rate tracker associated with this connection; keeps @@ -583,8 +594,9 @@ public class DoSFilter implements Filter { boolean allowed = checkWhitelist(request.getRemoteAddr()); int maxRequestsPerSec = getMaxRequestsPerSec(); - tracker = allowed ? new FixedRateTracker(loadId, type, maxRequestsPerSec) - : new RateTracker(loadId, type, maxRequestsPerSec); + tracker = allowed ? new FixedRateTracker(_context, _name, loadId, type, maxRequestsPerSec) + : new RateTracker(_context,_name, loadId, type, maxRequestsPerSec); + tracker.setContext(_context); RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker); if (existing != null) tracker = existing; @@ -603,6 +615,16 @@ public class DoSFilter implements Filter return tracker; } + + public void addToRateTracker (RateTracker tracker) + { + _rateTrackers.put(tracker.getId(), tracker); + } + + public void removeFromRateTracker (String id) + { + _rateTrackers.remove(id); + } protected boolean checkWhitelist(String candidate) { @@ -931,6 +953,25 @@ public class DoSFilter implements Filter _maxIdleTrackerMs = value; } + /** + * The unique name of the filter when there is more than + * one DosFilter instance. + * + * @return the name + */ + public String getName() + { + return _name; + } + + /** + * @param name the name to set + */ + public void setName(String name) + { + _name = name; + } + /** * Check flag to insert the DoSFilter headers into the response. * @@ -1103,17 +1144,22 @@ public class DoSFilter implements Filter * A RateTracker is associated with a connection, and stores request rate * data. */ - class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable + static class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable { private static final long serialVersionUID = 3534663738034577872L; + protected final String _filterName; + protected transient ServletContext _context; protected final String _id; protected final int _type; protected final long[] _timestamps; + protected int _next; - public RateTracker(String id, int type, int maxRequestsPerSecond) + public RateTracker(ServletContext context, String filterName, String id, int type, int maxRequestsPerSecond) { + _context = context; + _filterName = filterName; _id = id; _type = type; _timestamps = new long[maxRequestsPerSecond]; @@ -1151,40 +1197,87 @@ public class DoSFilter implements Filter { if (LOG.isDebugEnabled()) LOG.debug("Value bound: {}", getId()); + _context = event.getSession().getServletContext(); } public void valueUnbound(HttpSessionBindingEvent event) { //take the tracker out of the list of trackers - _rateTrackers.remove(_id); - if (LOG.isDebugEnabled()) - LOG.debug("Tracker removed: {}", getId()); + DoSFilter filter = (DoSFilter)event.getSession().getServletContext().getAttribute(_filterName); + removeFromRateTrackers(filter, _id); + _context = null; } public void sessionWillPassivate(HttpSessionEvent se) { //take the tracker of the list of trackers (if its still there) - _rateTrackers.remove(_id); + DoSFilter filter = (DoSFilter)se.getSession().getServletContext().getAttribute(_filterName); + removeFromRateTrackers(filter, _id); + _context = null; } public void sessionDidActivate(HttpSessionEvent se) { RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER); - if (tracker!=null) - _rateTrackers.put(tracker.getId(),tracker); + ServletContext context = se.getSession().getServletContext(); + tracker.setContext(context); + DoSFilter filter = (DoSFilter)context.getAttribute(_filterName); + if (filter == null) + { + LOG.info("No filter {} for rate tracker {}", _filterName, tracker); + return; + } + addToRateTrackers(filter, tracker); + } + + public void setContext (ServletContext context) + { + _context = context; + } + + + protected void removeFromRateTrackers (DoSFilter filter, String id) + { + if (filter == null) + return; + + filter.removeFromRateTracker(id); + if (LOG.isDebugEnabled()) + LOG.debug("Tracker removed: {}", getId()); + } + + + protected void addToRateTrackers (DoSFilter filter, RateTracker tracker) + { + if (filter == null) + return; + filter.addToRateTracker(tracker); } @Override public void run() { + if (_context == null) + { + LOG.warn("Unknkown context for rate tracker {}", this); + return; + } + int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1); long last = _timestamps[latestIndex]; boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L; + DoSFilter filter = (DoSFilter)_context.getAttribute(_filterName); + if (hasRecentRequest) - _scheduler.schedule(this, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS); + { + if (filter != null) + filter.schedule(this); + else + LOG.warn("No filter {}", _filterName); + } else - _rateTrackers.remove(_id); + removeFromRateTrackers(filter, _id); } @Override @@ -1196,9 +1289,9 @@ public class DoSFilter implements Filter class FixedRateTracker extends RateTracker { - public FixedRateTracker(String id, int type, int numRecentRequestsTracked) + public FixedRateTracker(ServletContext context, String filterName, String id, int type, int numRecentRequestsTracked) { - super(id, type, numRecentRequestsTracked); + super(context, filterName, id, type, numRecentRequestsTracked); } @Override diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java index 74141ddc872..d1c8f7810bb 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/AbstractDoSFilterTest.java @@ -18,11 +18,13 @@ package org.eclipse.jetty.servlets; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.charset.StandardCharsets; +import java.nio.file.Path; import java.util.EnumSet; import javax.servlet.DispatcherType; @@ -34,12 +36,17 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.http.HttpURI; +import org.eclipse.jetty.server.session.DefaultSessionCache; +import org.eclipse.jetty.server.session.FileSessionDataStore; import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.servlet.ServletTester; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.TestingDir; import org.eclipse.jetty.util.IO; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Assert; +import org.junit.Rule; import org.junit.Test; public abstract class AbstractDoSFilterTest @@ -49,9 +56,23 @@ public abstract class AbstractDoSFilterTest protected int _port; protected long _requestMaxTime = 200; + @Rule + public TestingDir _testDir = new TestingDir(); + public void startServer(Class filter) throws Exception { _tester = new ServletTester("/ctx"); + + DefaultSessionCache sessionCache = new DefaultSessionCache(_tester.getContext().getSessionHandler()); + FileSessionDataStore fileStore = new FileSessionDataStore(); + + Path p = _testDir.getPathFile("sessions"); + FS.ensureEmpty(p); + fileStore.setStoreDir(p.toFile()); + sessionCache.setSessionDataStore(fileStore); + + _tester.getContext().getSessionHandler().setSessionCache(sessionCache); + HttpURI uri = new HttpURI(_tester.createConnector(true)); _host = uri.getHost(); _port = uri.getPort(); diff --git a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java index 72bec4061c6..bbbe09bb5e2 100644 --- a/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java +++ b/jetty-servlets/src/test/java/org/eclipse/jetty/servlets/DoSFilterTest.java @@ -18,6 +18,9 @@ package org.eclipse.jetty.servlets; +import javax.servlet.ServletContext; + +import org.eclipse.jetty.server.handler.ContextHandler; import org.eclipse.jetty.servlets.DoSFilter.RateTracker; import org.hamcrest.Matchers; import org.junit.Assert; @@ -36,7 +39,7 @@ public class DoSFilterTest extends AbstractDoSFilterTest public void testRateIsRateExceeded() throws InterruptedException { DoSFilter doSFilter = new DoSFilter(); - + doSFilter.setName("foo"); boolean exceeded = hitRateTracker(doSFilter,0); Assert.assertTrue("Last hit should have exceeded",exceeded); @@ -49,6 +52,7 @@ public class DoSFilterTest extends AbstractDoSFilterTest public void testWhitelist() throws Exception { DoSFilter filter = new DoSFilter(); + filter.setName("foo"); filter.setWhitelist("192.168.0.1/32,10.0.0.0/8,4d8:0:a:1234:ABc:1F:b18:17,4d8:0:a:1234:ABc:1F:0:0/96"); Assert.assertTrue(filter.checkWhitelist("192.168.0.1")); Assert.assertFalse(filter.checkWhitelist("192.168.0.2")); @@ -72,7 +76,8 @@ public class DoSFilterTest extends AbstractDoSFilterTest private boolean hitRateTracker(DoSFilter doSFilter, int sleep) throws InterruptedException { boolean exceeded = false; - RateTracker rateTracker = doSFilter.new RateTracker("test2",0,4); + ServletContext context = new ContextHandler.StaticContext(); + RateTracker rateTracker = new RateTracker(context, doSFilter.getName(), "test2",0,4); for (int i = 0; i < 5; i++) {