This is an automated email from the ASF dual-hosted git repository. markt pushed a commit to branch 9.0.x in repository https://gitbox.apache.org/repos/asf/tomcat.git
commit 61f237e4a6b771a586802b19d5b460dce52d0c0d Author: Mark Thomas <ma...@apache.org> AuthorDate: Tue May 9 20:38:49 2023 +0100 Clean-up - formatting. No functional change. --- .../apache/catalina/filters/RateLimitFilter.java | 91 ++++++++++------------ .../apache/catalina/util/TimeBucketCounter.java | 60 ++++++-------- .../catalina/filters/TestRateLimitFilter.java | 14 ++-- 3 files changed, 73 insertions(+), 92 deletions(-) diff --git a/java/org/apache/catalina/filters/RateLimitFilter.java b/java/org/apache/catalina/filters/RateLimitFilter.java index 7dd4f7a3a6..d164eee94e 100644 --- a/java/org/apache/catalina/filters/RateLimitFilter.java +++ b/java/org/apache/catalina/filters/RateLimitFilter.java @@ -33,45 +33,41 @@ import org.apache.juli.logging.LogFactory; import org.apache.tomcat.util.res.StringManager; /** - * <p>Servlet filter that can help mitigate Denial of Service - * (DoS) and Brute Force attacks by limiting the number of a requests that are - * allowed from a single IP address within a time window (also referred - * to as a time bucket), e.g. 300 Requests per 60 seconds.</p> - * - * <p>The filter works by incrementing a counter in a time bucket for each IP - * address, and if the counter exceeds the allowed limit then further requests - * from that IP are dropped with a "429 Too many requests" response - * until the bucket time ends and a new bucket starts.</p> - * - * <p>The filter is optimized for efficiency and low overhead, so it converts - * some configured values to more efficient values. For example, a configuration - * of a 60 seconds time bucket is converted to 65.536 seconds. That allows - * for very fast bucket calculation using bit shift arithmetic. In order to remain - * true to the user intent, the configured number of requests is then multiplied - * by the same ratio, so a configuration of 100 Requests per 60 seconds, has the - * real values of 109 Requests per 65 seconds.</p> - * - * <p>It is common to set up different restrictions for different URIs. - * For example, a login page or authentication script is typically expected - * to get far less requests than the rest of the application, so you can add - * a filter definition that would allow only 5 requests per 15 seconds and map - * those URIs to it.</p> - * - * <p>You can set <code>enforce</code> to <code>false</code> - * to disable the termination of requests that exceed the allowed limit. Then - * your application code can inspect the Request Attribute - * <code>org.apache.catalina.filters.RateLimitFilter.Count</code> and decide - * how to handle the request based on other information that it has, e.g. allow - * more requests to certain users based on roles, etc.</p> - * - * <p><strong>WARNING:</strong> if Tomcat is behind a reverse proxy then you must - * make sure that the Rate Limit Filter sees the client IP address, so if for - * example you are using the <a href="#Remote_IP_Filter">Remote IP Filter</a>, - * then the filter mapping for the Rate Limit Filter must come <em>after</em> - * the mapping of the Remote IP Filter to ensure that each request has its IP - * address resolved before the Rate Limit Filter is applied. Failure to do so - * will count requests from different IPs in the same bucket and will result in - * a self inflicted DoS attack.</p> + * <p> + * Servlet filter that can help mitigate Denial of Service (DoS) and Brute Force attacks by limiting the number of a + * requests that are allowed from a single IP address within a time window (also referred to as a time bucket), e.g. 300 + * Requests per 60 seconds. + * </p> + * <p> + * The filter works by incrementing a counter in a time bucket for each IP address, and if the counter exceeds the + * allowed limit then further requests from that IP are dropped with a "429 Too many requests" response until + * the bucket time ends and a new bucket starts. + * </p> + * <p> + * The filter is optimized for efficiency and low overhead, so it converts some configured values to more efficient + * values. For example, a configuration of a 60 seconds time bucket is converted to 65.536 seconds. That allows for very + * fast bucket calculation using bit shift arithmetic. In order to remain true to the user intent, the configured number + * of requests is then multiplied by the same ratio, so a configuration of 100 Requests per 60 seconds, has the real + * values of 109 Requests per 65 seconds. + * </p> + * <p> + * It is common to set up different restrictions for different URIs. For example, a login page or authentication script + * is typically expected to get far less requests than the rest of the application, so you can add a filter definition + * that would allow only 5 requests per 15 seconds and map those URIs to it. + * </p> + * <p> + * You can set <code>enforce</code> to <code>false</code> to disable the termination of requests that exceed the allowed + * limit. Then your application code can inspect the Request Attribute + * <code>org.apache.catalina.filters.RateLimitFilter.Count</code> and decide how to handle the request based on other + * information that it has, e.g. allow more requests to certain users based on roles, etc. + * </p> + * <p> + * <strong>WARNING:</strong> if Tomcat is behind a reverse proxy then you must make sure that the Rate Limit Filter sees + * the client IP address, so if for example you are using the <a href="#Remote_IP_Filter">Remote IP Filter</a>, then the + * filter mapping for the Rate Limit Filter must come <em>after</em> the mapping of the Remote IP Filter to ensure that + * each request has its IP address resolved before the Rate Limit Filter is applied. Failure to do so will count + * requests from different IPs in the same bucket and will result in a self inflicted DoS attack. + * </p> */ public class RateLimitFilter extends GenericFilter { @@ -199,16 +195,14 @@ public class RateLimitFilter extends GenericFilter { actualRequests = (int) Math.round(bucketCounter.getRatio() * bucketRequests); - log.info(sm.getString("rateLimitFilter.initialized", - super.getFilterName(), Integer.valueOf(bucketRequests), Integer.valueOf(bucketDuration), - Integer.valueOf(getActualRequests()), Integer.valueOf(getActualDurationInSeconds()), - (!enforce ? "Not " : "") + "enforcing") - ); + log.info(sm.getString("rateLimitFilter.initialized", super.getFilterName(), Integer.valueOf(bucketRequests), + Integer.valueOf(bucketDuration), Integer.valueOf(getActualRequests()), + Integer.valueOf(getActualDurationInSeconds()), (!enforce ? "Not " : "") + "enforcing")); } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) - throws IOException, ServletException { + throws IOException, ServletException { String ipAddr = request.getRemoteAddr(); int reqCount = bucketCounter.increment(ipAddr); @@ -218,10 +212,9 @@ public class RateLimitFilter extends GenericFilter { if (enforce && (reqCount > actualRequests)) { ((HttpServletResponse) response).sendError(statusCode, statusMessage); - log.warn(sm.getString("rateLimitFilter.maxRequestsExceeded", - super.getFilterName(), Integer.valueOf(reqCount), ipAddr, Integer.valueOf(getActualRequests()), - Integer.valueOf(getActualDurationInSeconds())) - ); + log.warn(sm.getString("rateLimitFilter.maxRequestsExceeded", super.getFilterName(), + Integer.valueOf(reqCount), ipAddr, Integer.valueOf(getActualRequests()), + Integer.valueOf(getActualDurationInSeconds()))); return; } diff --git a/java/org/apache/catalina/util/TimeBucketCounter.java b/java/org/apache/catalina/util/TimeBucketCounter.java index 4c1974242e..9a472523c7 100644 --- a/java/org/apache/catalina/util/TimeBucketCounter.java +++ b/java/org/apache/catalina/util/TimeBucketCounter.java @@ -21,34 +21,29 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; /** - * this class maintains a thread safe hash map that has timestamp-based buckets - * followed by a string for a key, and a counter for a value. each time the - * increment() method is called it adds the key if it does not exist, increments - * its value and returns it. - * - * a maintenance thread cleans up keys that are prefixed by previous timestamp - * buckets. + * This class maintains a thread safe hash map that has timestamp-based buckets followed by a string for a key, and a + * counter for a value. each time the increment() method is called it adds the key if it does not exist, increments its + * value and returns it. a maintenance thread cleans up keys that are prefixed by previous timestamp buckets. */ public class TimeBucketCounter { /** * Map to hold the buckets */ - private final ConcurrentHashMap<String, AtomicInteger> map = new ConcurrentHashMap<>(); + private final ConcurrentHashMap<String,AtomicInteger> map = new ConcurrentHashMap<>(); /** - * Milliseconds bucket size as a Power of 2 for bit shift math, e.g. - * 16 for 65_536ms which is about 1:05 minute + * Milliseconds bucket size as a Power of 2 for bit shift math, e.g. 16 for 65_536ms which is about 1:05 minute */ private final int numBits; /** - * ratio of actual duration to config duration + * Ratio of actual duration to config duration */ private final double ratio; /** - * flag for the maintenance thread + * Flag for the maintenance thread */ volatile boolean isRunning = false; @@ -78,10 +73,10 @@ public class TimeBucketCounter { } /** - * increments the counter for the passed identifier in the current time - * bucket and returns the new value + * Increments the counter for the passed identifier in the current time bucket and returns the new value. * * @param identifier an identifier for which we want to maintain count, e.g. IP Address + * * @return the count within the current time bucket */ public final int increment(String identifier) { @@ -91,9 +86,8 @@ public class TimeBucketCounter { } /** - * calculates the current time bucket prefix by shifting bits for fast - * division, e.g. shift 16 bits is the same as dividing by 65,536 which is - * about 1:05m + * Calculates the current time bucket prefix by shifting bits for fast division, e.g. shift 16 bits is the same as + * dividing by 65,536 which is about 1:05m. * * @return The current bucket prefix. */ @@ -106,9 +100,8 @@ public class TimeBucketCounter { } /** - * the actual duration may differ from the configured duration because - * it is set to the next power of 2 value in order to perform very fast - * bit shift arithmetic + * The actual duration may differ from the configured duration because it is set to the next power of 2 value in + * order to perform very fast bit shift arithmetic. * * @return the actual bucket duration in milliseconds */ @@ -117,20 +110,18 @@ public class TimeBucketCounter { } /** - * returns the ratio between the configured duration param and the - * actual duration which will be set to the next power of 2. we then - * multiply the configured requests param by the same ratio in order - * to compensate for the added time, if any + * Returns the ratio between the configured duration param and the actual duration which will be set to the next + * power of 2. We then multiply the configured requests param by the same ratio in order to compensate for the added + * time, if any. * - * @return the ratio, e.g. 1.092 if the actual duration is 65_536 for - * the configured duration of 60_000 + * @return the ratio, e.g. 1.092 if the actual duration is 65_536 for the configured duration of 60_000 */ public double getRatio() { return ratio; } /** - * returns the ratio to the next power of 2 so that we can adjust the value + * Returns the ratio to the next power of 2 so that we can adjust the value. */ static double ratioToPowerOf2(int value) { double nextPO2 = nextPowerOf2(value); @@ -138,8 +129,7 @@ public class TimeBucketCounter { } /** - * returns the next power of 2 given a value, e.g. 256 for 250, - * or 1024, for 1000 + * Returns the next power of 2 given a value, e.g. 256 for 250, or 1024, for 1000. */ static int nextPowerOf2(int value) { int valueOfHighestBit = Integer.highestOneBit(value); @@ -151,8 +141,7 @@ public class TimeBucketCounter { } /** - * when we want to test a full bucket duration we need to sleep until the - * next bucket starts + * When we want to test a full bucket duration we need to sleep until the next bucket starts. * * @return the number of milliseconds until the next bucket */ @@ -164,14 +153,14 @@ public class TimeBucketCounter { } /** - * sets isRunning to false to terminate the maintenance thread + * Sets isRunning to false to terminate the maintenance thread. */ public void destroy() { this.isRunning = false; } /** - * this class runs a background thread to clean up old keys from the map + * This class runs a background thread to clean up old keys from the map. */ class MaintenanceThread extends Thread { @@ -194,14 +183,15 @@ public class TimeBucketCounter { while (isRunning) { String currentBucketPrefix = String.valueOf(getCurrentBucketPrefix()); - ConcurrentHashMap.KeySetView<String, AtomicInteger> keys = map.keySet(); + ConcurrentHashMap.KeySetView<String,AtomicInteger> keys = map.keySet(); // remove obsolete keys keys.removeIf(k -> !k.startsWith(currentBucketPrefix)); try { Thread.sleep(sleeptime); - } catch (InterruptedException e) {} + } catch (InterruptedException e) { + } } } } diff --git a/test/org/apache/catalina/filters/TestRateLimitFilter.java b/test/org/apache/catalina/filters/TestRateLimitFilter.java index ecef77d0ab..3d01942101 100644 --- a/test/org/apache/catalina/filters/TestRateLimitFilter.java +++ b/test/org/apache/catalina/filters/TestRateLimitFilter.java @@ -73,15 +73,15 @@ public class TestRateLimitFilter extends TomcatBaseTest { Thread.sleep(5000); - Assert.assertEquals(200, tc1.results[24]); // only 25 requests made, all allowed + Assert.assertEquals(200, tc1.results[24]); // only 25 requests made, all allowed - Assert.assertEquals(200, tc2.results[49]); // only 25 requests made, all allowed + Assert.assertEquals(200, tc2.results[49]); // only 25 requests made, all allowed Assert.assertEquals(200, tc3.results[allowedRequests - 1]); // first allowedRequests allowed - Assert.assertEquals(429, tc3.results[allowedRequests]); // subsequent requests dropped + Assert.assertEquals(429, tc3.results[allowedRequests]); // subsequent requests dropped Assert.assertEquals(200, tc4.results[allowedRequests - 1]); // first allowedRequests allowed - Assert.assertEquals(429, tc4.results[allowedRequests]); // subsequent requests dropped + Assert.assertEquals(429, tc4.results[allowedRequests]); // subsequent requests dropped } private RateLimitFilter testRateLimitFilter(FilterDef filterDef, Context root) throws ServletException { @@ -102,7 +102,6 @@ public class TestRateLimitFilter extends TomcatBaseTest { rateLimitFilter.init(filterConfig); return rateLimitFilter; - //*/ } static class TestClient extends Thread { @@ -140,8 +139,7 @@ public class TestRateLimitFilter extends TomcatBaseTest { Integer.valueOf(response.getStatus())); Thread.sleep(sleep); } - } - catch (Exception ex) { + } catch (Exception ex) { ex.printStackTrace(); } } @@ -167,7 +165,7 @@ public class TestRateLimitFilter extends TomcatBaseTest { private static FilterConfig generateFilterConfig(FilterDef filterDef) { TesterServletContext mockServletContext = new TesterServletContext(); - Map<String, String> parameters = filterDef.getParameterMap(); + Map<String,String> parameters = filterDef.getParameterMap(); FilterConfig filterConfig = new FilterConfig() { --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org