View Javadoc
1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.servlets;
20  
21  import java.io.IOException;
22  import java.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Queue;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentLinkedQueue;
29  import java.util.concurrent.CopyOnWriteArrayList;
30  import java.util.concurrent.Semaphore;
31  import java.util.concurrent.TimeUnit;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import javax.servlet.AsyncContext;
36  import javax.servlet.AsyncEvent;
37  import javax.servlet.AsyncListener;
38  import javax.servlet.DispatcherType;
39  import javax.servlet.Filter;
40  import javax.servlet.FilterChain;
41  import javax.servlet.FilterConfig;
42  import javax.servlet.ServletContext;
43  import javax.servlet.ServletException;
44  import javax.servlet.ServletRequest;
45  import javax.servlet.ServletResponse;
46  import javax.servlet.http.HttpServletRequest;
47  import javax.servlet.http.HttpServletResponse;
48  import javax.servlet.http.HttpSession;
49  import javax.servlet.http.HttpSessionActivationListener;
50  import javax.servlet.http.HttpSessionBindingEvent;
51  import javax.servlet.http.HttpSessionBindingListener;
52  import javax.servlet.http.HttpSessionEvent;
53  
54  import org.eclipse.jetty.http.HttpStatus;
55  import org.eclipse.jetty.server.handler.ContextHandler;
56  import org.eclipse.jetty.util.StringUtil;
57  import org.eclipse.jetty.util.annotation.ManagedAttribute;
58  import org.eclipse.jetty.util.annotation.ManagedObject;
59  import org.eclipse.jetty.util.annotation.ManagedOperation;
60  import org.eclipse.jetty.util.annotation.Name;
61  import org.eclipse.jetty.util.log.Log;
62  import org.eclipse.jetty.util.log.Logger;
63  import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
64  import org.eclipse.jetty.util.thread.Scheduler;
65  
66  /**
67   * Denial of Service filter
68   * <p>
69   * This filter is useful for limiting
70   * exposure to abuse from request flooding, whether malicious, or as a result of
71   * a misconfigured client.
72   * <p>
73   * The filter keeps track of the number of requests from a connection per
74   * second. If a limit is exceeded, the request is either rejected, delayed, or
75   * throttled.
76   * <p>
77   * When a request is throttled, it is placed in a priority queue. Priority is
78   * given first to authenticated users and users with an HttpSession, then
79   * connections which can be identified by their IP addresses. Connections with
80   * no way to identify them are given lowest priority.
81   * <p>
82   * The {@link #extractUserId(ServletRequest request)} function should be
83   * implemented, in order to uniquely identify authenticated users.
84   * <p>
85   * The following init parameters control the behavior of the filter:
86   * <dl>
87   * <dt>maxRequestsPerSec</dt>
88   * <dd>the maximum number of requests from a connection per
89   * second. Requests in excess of this are first delayed,
90   * then throttled.</dd>
91   * <dt>delayMs</dt>
92   * <dd>is the delay given to all requests over the rate limit,
93   * before they are considered at all. -1 means just reject request,
94   * 0 means no delay, otherwise it is the delay.</dd>
95   * <dt>maxWaitMs</dt>
96   * <dd>how long to blocking wait for the throttle semaphore.</dd>
97   * <dt>throttledRequests</dt>
98   * <dd>is the number of requests over the rate limit able to be
99   * considered at once.</dd>
100  * <dt>throttleMs</dt>
101  * <dd>how long to async wait for semaphore.</dd>
102  * <dt>maxRequestMs</dt>
103  * <dd>how long to allow this request to run.</dd>
104  * <dt>maxIdleTrackerMs</dt>
105  * <dd>how long to keep track of request rates for a connection,
106  * before deciding that the user has gone away, and discarding it</dd>
107  * <dt>insertHeaders</dt>
108  * <dd>if true , insert the DoSFilter headers into the response. Defaults to true.</dd>
109  * <dt>trackSessions</dt>
110  * <dd>if true, usage rate is tracked by session if a session exists. Defaults to true.</dd>
111  * <dt>remotePort</dt>
112  * <dd>if true and session tracking is not used, then rate is tracked by IP+port (effectively connection). Defaults to false.</dd>
113  * <dt>ipWhitelist</dt>
114  * <dd>a comma-separated list of IP addresses that will not be rate limited</dd>
115  * <dt>managedAttr</dt>
116  * <dd>if set to true, then this servlet is set as a {@link ServletContext} attribute with the
117  * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
118  * manage the configuration of the filter.</dd>
119  * <dt>tooManyCode</dt>
120  * <dd>The status code to send if there are too many requests.  By default is 429 (too many requests), but 503 (Unavailable) is
121  * another option</dd>
122  * </dl>
123  * <p>
124  * This filter should be configured for {@link DispatcherType#REQUEST} and {@link DispatcherType#ASYNC} and with
125  * <code>&lt;async-supported&gt;true&lt;/async-supported&gt;</code>.
126  * </p>
127  */
128 @ManagedObject("limits exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client")
129 public class DoSFilter implements Filter
130 {
131     private static final Logger LOG = Log.getLogger(DoSFilter.class);
132 
133     private static final String IPv4_GROUP = "(\\d{1,3})";
134     private static final Pattern IPv4_PATTERN = Pattern.compile(IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP);
135     private static final String IPv6_GROUP = "(\\p{XDigit}{1,4})";
136     private static final Pattern IPv6_PATTERN = Pattern.compile(IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP);
137     private static final Pattern CIDR_PATTERN = Pattern.compile("([^/]+)/(\\d+)");
138 
139     private static final String __TRACKER = "DoSFilter.Tracker";
140     private static final String __THROTTLED = "DoSFilter.Throttled";
141 
142     private static final int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
143     private static final int __DEFAULT_DELAY_MS = 100;
144     private static final int __DEFAULT_THROTTLE = 5;
145     private static final int __DEFAULT_MAX_WAIT_MS = 50;
146     private static final long __DEFAULT_THROTTLE_MS = 30000L;
147     private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L;
148     private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L;
149 
150     static final String MANAGED_ATTR_INIT_PARAM = "managedAttr";
151     static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
152     static final String DELAY_MS_INIT_PARAM = "delayMs";
153     static final String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
154     static final String MAX_WAIT_INIT_PARAM = "maxWaitMs";
155     static final String THROTTLE_MS_INIT_PARAM = "throttleMs";
156     static final String MAX_REQUEST_MS_INIT_PARAM = "maxRequestMs";
157     static final String MAX_IDLE_TRACKER_MS_INIT_PARAM = "maxIdleTrackerMs";
158     static final String INSERT_HEADERS_INIT_PARAM = "insertHeaders";
159     static final String TRACK_SESSIONS_INIT_PARAM = "trackSessions";
160     static final String REMOTE_PORT_INIT_PARAM = "remotePort";
161     static final String IP_WHITELIST_INIT_PARAM = "ipWhitelist";
162     static final String ENABLED_INIT_PARAM = "enabled";
163     static final String TOO_MANY_CODE = "tooManyCode";
164 
165     private static final int USER_AUTH = 2;
166     private static final int USER_SESSION = 2;
167     private static final int USER_IP = 1;
168     private static final int USER_UNKNOWN = 0;
169 
170     private final String _suspended = "DoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED";
171     private final String _resumed = "DoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED";
172     private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<>();
173     private final List<String> _whitelist = new CopyOnWriteArrayList<>();
174     private int _tooManyCode;
175     private volatile long _delayMs;
176     private volatile long _throttleMs;
177     private volatile long _maxWaitMs;
178     private volatile long _maxRequestMs;
179     private volatile long _maxIdleTrackerMs;
180     private volatile boolean _insertHeaders;
181     private volatile boolean _trackSessions;
182     private volatile boolean _remotePort;
183     private volatile boolean _enabled;
184     private Semaphore _passes;
185     private volatile int _throttledRequests;
186     private volatile int _maxRequestsPerSec;
187     private Queue<AsyncContext>[] _queues;
188     private AsyncListener[] _listeners;
189     private Scheduler _scheduler;
190 
191     public void init(FilterConfig filterConfig) throws ServletException
192     {
193         _queues = new Queue[getMaxPriority() + 1];
194         _listeners = new AsyncListener[_queues.length];
195         for (int p = 0; p < _queues.length; p++)
196         {
197             _queues[p] = new ConcurrentLinkedQueue<>();
198             _listeners[p] = new DoSAsyncListener(p);
199         }
200 
201         _rateTrackers.clear();
202 
203         int maxRequests = __DEFAULT_MAX_REQUESTS_PER_SEC;
204         String parameter = filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM);
205         if (parameter != null)
206             maxRequests = Integer.parseInt(parameter);
207         setMaxRequestsPerSec(maxRequests);
208 
209         long delay = __DEFAULT_DELAY_MS;
210         parameter = filterConfig.getInitParameter(DELAY_MS_INIT_PARAM);
211         if (parameter != null)
212             delay = Long.parseLong(parameter);
213         setDelayMs(delay);
214 
215         int throttledRequests = __DEFAULT_THROTTLE;
216         parameter = filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM);
217         if (parameter != null)
218             throttledRequests = Integer.parseInt(parameter);
219         setThrottledRequests(throttledRequests);
220 
221         long maxWait = __DEFAULT_MAX_WAIT_MS;
222         parameter = filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM);
223         if (parameter != null)
224             maxWait = Long.parseLong(parameter);
225         setMaxWaitMs(maxWait);
226 
227         long throttle = __DEFAULT_THROTTLE_MS;
228         parameter = filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM);
229         if (parameter != null)
230             throttle = Long.parseLong(parameter);
231         setThrottleMs(throttle);
232 
233         long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
234         parameter = filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM);
235         if (parameter != null)
236             maxRequestMs = Long.parseLong(parameter);
237         setMaxRequestMs(maxRequestMs);
238 
239         long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
240         parameter = filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM);
241         if (parameter != null)
242             maxIdleTrackerMs = Long.parseLong(parameter);
243         setMaxIdleTrackerMs(maxIdleTrackerMs);
244 
245         String whiteList = "";
246         parameter = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
247         if (parameter != null)
248             whiteList = parameter;
249         setWhitelist(whiteList);
250 
251         parameter = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
252         setInsertHeaders(parameter == null || Boolean.parseBoolean(parameter));
253 
254         parameter = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
255         setTrackSessions(parameter == null || Boolean.parseBoolean(parameter));
256 
257         parameter = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
258         setRemotePort(parameter != null && Boolean.parseBoolean(parameter));
259 
260         parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
261         setEnabled(parameter == null || Boolean.parseBoolean(parameter));
262 
263         parameter = filterConfig.getInitParameter(TOO_MANY_CODE);
264         setTooManyCode(parameter==null?429:Integer.parseInt(parameter));
265 
266         _scheduler = startScheduler();
267 
268         ServletContext context = filterConfig.getServletContext();
269         if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
270             context.setAttribute(filterConfig.getFilterName(), this);
271     }
272 
273     protected Scheduler startScheduler() throws ServletException
274     {
275         try
276         {
277             Scheduler result = new ScheduledExecutorScheduler();
278             result.start();
279             return result;
280         }
281         catch (Exception x)
282         {
283             throw new ServletException(x);
284         }
285     }
286 
287     public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
288     {
289         doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain);
290     }
291 
292     protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException
293     {
294         if (!isEnabled())
295         {
296             filterChain.doFilter(request, response);
297             return;
298         }
299 
300         // Look for the rate tracker for this request.
301         RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
302         if (tracker == null)
303         {
304             // This is the first time we have seen this request.
305             if (LOG.isDebugEnabled())
306                 LOG.debug("Filtering {}", request);
307 
308             // Get a rate tracker associated with this request, and record one hit.
309             tracker = getRateTracker(request);
310 
311             // Calculate the rate and check it is over the allowed limit
312             final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis());
313 
314             // Pass it through if  we are not currently over the rate limit.
315             if (!overRateLimit)
316             {
317                 if (LOG.isDebugEnabled())
318                     LOG.debug("Allowing {}", request);
319                 doFilterChain(filterChain, request, response);
320                 return;
321             }
322 
323             // We are over the limit.
324 
325             // So either reject it, delay it or throttle it.
326             long delayMs = getDelayMs();
327             boolean insertHeaders = isInsertHeaders();
328             switch ((int)delayMs)
329             {
330                 case -1:
331                 {
332                     // Reject this request.
333                     LOG.warn("DOS ALERT: Request rejected ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
334                     if (insertHeaders)
335                         response.addHeader("DoSFilter", "unavailable");
336                     response.sendError(getTooManyCode());
337                     return;
338                 }
339                 case 0:
340                 {
341                     // Fall through to throttle the request.
342                     LOG.warn("DOS ALERT: Request throttled ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
343                     request.setAttribute(__TRACKER, tracker);
344                     break;
345                 }
346                 default:
347                 {
348                     // Insert a delay before throttling the request,
349                     // using the suspend+timeout mechanism of AsyncContext.
350                     LOG.warn("DOS ALERT: Request delayed={}ms, ip={}, session={}, user={}", delayMs, request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
351                     if (insertHeaders)
352                         response.addHeader("DoSFilter", "delayed");
353                     request.setAttribute(__TRACKER, tracker);
354                     AsyncContext asyncContext = request.startAsync();
355                     if (delayMs > 0)
356                         asyncContext.setTimeout(delayMs);
357                     asyncContext.addListener(new DoSTimeoutAsyncListener());
358                     return;
359                 }
360             }
361         }
362 
363         if (LOG.isDebugEnabled())
364             LOG.debug("Throttling {}", request);
365 
366         // Throttle the request.
367         boolean accepted = false;
368         try
369         {
370             // Check if we can afford to accept another request at this time.
371             accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
372             if (!accepted)
373             {
374                 // We were not accepted, so either we suspend to wait,
375                 // or if we were woken up we insist or we fail.
376                 Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
377                 long throttleMs = getThrottleMs();
378                 if (throttled != Boolean.TRUE && throttleMs > 0)
379                 {
380                     int priority = getPriority(request, tracker);
381                     request.setAttribute(__THROTTLED, Boolean.TRUE);
382                     if (isInsertHeaders())
383                         response.addHeader("DoSFilter", "throttled");
384                     AsyncContext asyncContext = request.startAsync();
385                     request.setAttribute(_suspended, Boolean.TRUE);
386                     if (throttleMs > 0)
387                         asyncContext.setTimeout(throttleMs);
388                     asyncContext.addListener(_listeners[priority]);
389                     _queues[priority].add(asyncContext);
390                     if (LOG.isDebugEnabled())
391                         LOG.debug("Throttled {}, {}ms", request, throttleMs);
392                     return;
393                 }
394 
395                 Boolean resumed = (Boolean)request.getAttribute(_resumed);
396                 if (resumed == Boolean.TRUE)
397                 {
398                     // We were resumed, we wait for the next pass.
399                     _passes.acquire();
400                     accepted = true;
401                 }
402             }
403 
404             // If we were accepted (either immediately or after throttle)...
405             if (accepted)
406             {
407                 // ...call the chain.
408                 if (LOG.isDebugEnabled())
409                     LOG.debug("Allowing {}", request);
410                 doFilterChain(filterChain, request, response);
411             }
412             else
413             {
414                 // ...otherwise fail the request.
415                 if (LOG.isDebugEnabled())
416                     LOG.debug("Rejecting {}", request);
417                 if (isInsertHeaders())
418                     response.addHeader("DoSFilter", "unavailable");
419                 response.sendError(getTooManyCode());
420             }
421         }
422         catch (InterruptedException e)
423         {
424             LOG.ignore(e);
425             response.sendError(getTooManyCode());
426         }
427         finally
428         {
429             if (accepted)
430             {
431                 try
432                 {
433                     // Wake up the next highest priority request.
434                     for (int p = _queues.length - 1; p >= 0; --p)
435                     {
436                         AsyncContext asyncContext = _queues[p].poll();
437                         if (asyncContext != null)
438                         {
439                             ServletRequest candidate = asyncContext.getRequest();
440                             Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
441                             if (suspended == Boolean.TRUE)
442                             {
443                                 if (LOG.isDebugEnabled())
444                                     LOG.debug("Resuming {}", request);
445                                 candidate.setAttribute(_resumed, Boolean.TRUE);
446                                 asyncContext.dispatch();
447                                 break;
448                             }
449                         }
450                     }
451                 }
452                 finally
453                 {
454                     _passes.release();
455                 }
456             }
457         }
458     }
459 
460     protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
461     {
462         final Thread thread = Thread.currentThread();
463         Runnable requestTimeout = new Runnable()
464         {
465             @Override
466             public void run()
467             {
468                 closeConnection(request, response, thread);
469             }
470         };
471         Scheduler.Task task = _scheduler.schedule(requestTimeout, getMaxRequestMs(), TimeUnit.MILLISECONDS);
472         try
473         {
474             chain.doFilter(request, response);
475         }
476         finally
477         {
478             task.cancel();
479         }
480     }
481 
482     /**
483      * Invoked when the request handling exceeds {@link #getMaxRequestMs()}.
484      * <p>
485      * By default, a HTTP 503 response is returned and the handling thread is interrupted.
486      *
487      * @param request  the current request
488      * @param response the current response
489      * @param handlingThread the handling thread
490      */
491     protected void onRequestTimeout(HttpServletRequest request, HttpServletResponse response, Thread handlingThread)
492     {
493         try
494         {
495             if (LOG.isDebugEnabled())
496                 LOG.debug("Timing out {}", request);
497             response.sendError(HttpStatus.SERVICE_UNAVAILABLE_503);
498         }
499         catch (Throwable x)
500         {
501             LOG.info(x);
502         }
503 
504         handlingThread.interrupt();
505     }
506 
507     /**
508      * @deprecated use {@link #onRequestTimeout(HttpServletRequest, HttpServletResponse, Thread)} instead
509      */
510     @Deprecated
511     protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
512     {
513         onRequestTimeout(request, response, thread);
514     }
515 
516     /**
517      * Get priority for this request, based on user type
518      *
519      * @param request the current request
520      * @param tracker the rate tracker for this request
521      * @return the priority for this request
522      */
523     protected int getPriority(HttpServletRequest request, RateTracker tracker)
524     {
525         if (extractUserId(request) != null)
526             return USER_AUTH;
527         if (tracker != null)
528             return tracker.getType();
529         return USER_UNKNOWN;
530     }
531 
532     /**
533      * @return the maximum priority that we can assign to a request
534      */
535     protected int getMaxPriority()
536     {
537         return USER_AUTH;
538     }
539 
540     /**
541      * Return a request rate tracker associated with this connection; keeps
542      * track of this connection's request rate. If this is not the first request
543      * from this connection, return the existing object with the stored stats.
544      * If it is the first request, then create a new request tracker.
545      * <p>
546      * Assumes that each connection has an identifying characteristic, and goes
547      * through them in order, taking the first that matches: user id (logged
548      * in), session id, client IP address. Unidentifiable connections are lumped
549      * into one.
550      * <p>
551      * When a session expires, its rate tracker is automatically deleted.
552      *
553      * @param request the current request
554      * @return the request rate tracker for the current connection
555      */
556     public RateTracker getRateTracker(ServletRequest request)
557     {
558         HttpSession session = ((HttpServletRequest)request).getSession(false);
559 
560         String loadId = extractUserId(request);
561         final int type;
562         if (loadId != null)
563         {
564             type = USER_AUTH;
565         }
566         else
567         {
568             if (isTrackSessions() && session != null && !session.isNew())
569             {
570                 loadId = session.getId();
571                 type = USER_SESSION;
572             }
573             else
574             {
575                 loadId = isRemotePort() ? (request.getRemoteAddr() + request.getRemotePort()) : request.getRemoteAddr();
576                 type = USER_IP;
577             }
578         }
579 
580         RateTracker tracker = _rateTrackers.get(loadId);
581 
582         if (tracker == null)
583         {
584             boolean allowed = checkWhitelist(request.getRemoteAddr());
585             int maxRequestsPerSec = getMaxRequestsPerSec();
586             tracker = allowed ? new FixedRateTracker(loadId, type, maxRequestsPerSec)
587                     : new RateTracker(loadId, type, maxRequestsPerSec);
588             RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
589             if (existing != null)
590                 tracker = existing;
591 
592             if (type == USER_IP)
593             {
594                 // USER_IP expiration from _rateTrackers is handled by the _scheduler
595                 _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
596             }
597             else if (session != null)
598             {
599                 // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
600                 session.setAttribute(__TRACKER, tracker);
601             }
602         }
603 
604         return tracker;
605     }
606 
607     protected boolean checkWhitelist(String candidate)
608     {
609         for (String address : _whitelist)
610         {
611             if (address.contains("/"))
612             {
613                 if (subnetMatch(address, candidate))
614                     return true;
615             }
616             else
617             {
618                 if (address.equals(candidate))
619                     return true;
620             }
621         }
622         return false;
623     }
624 
625     @Deprecated
626     protected boolean checkWhitelist(List<String> whitelist, String candidate)
627     {
628         for (String address : whitelist)
629         {
630             if (address.contains("/"))
631             {
632                 if (subnetMatch(address, candidate))
633                     return true;
634             }
635             else
636             {
637                 if (address.equals(candidate))
638                     return true;
639             }
640         }
641         return false;
642     }
643 
644     protected boolean subnetMatch(String subnetAddress, String address)
645     {
646         Matcher cidrMatcher = CIDR_PATTERN.matcher(subnetAddress);
647         if (!cidrMatcher.matches())
648             return false;
649 
650         String subnet = cidrMatcher.group(1);
651         int prefix;
652         try
653         {
654             prefix = Integer.parseInt(cidrMatcher.group(2));
655         }
656         catch (NumberFormatException x)
657         {
658             LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
659             return false;
660         }
661 
662         byte[] subnetBytes = addressToBytes(subnet);
663         if (subnetBytes == null)
664         {
665             LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
666             return false;
667         }
668         byte[] addressBytes = addressToBytes(address);
669         if (addressBytes == null)
670         {
671             LOG.info("Ignoring malformed remote address {}", address);
672             return false;
673         }
674 
675         // Comparing IPv4 with IPv6 ?
676         int length = subnetBytes.length;
677         if (length != addressBytes.length)
678             return false;
679 
680         byte[] mask = prefixToBytes(prefix, length);
681 
682         for (int i = 0; i < length; ++i)
683         {
684             if ((subnetBytes[i] & mask[i]) != (addressBytes[i] & mask[i]))
685                 return false;
686         }
687 
688         return true;
689     }
690 
691     private byte[] addressToBytes(String address)
692     {
693         Matcher ipv4Matcher = IPv4_PATTERN.matcher(address);
694         if (ipv4Matcher.matches())
695         {
696             byte[] result = new byte[4];
697             for (int i = 0; i < result.length; ++i)
698                 result[i] = Integer.valueOf(ipv4Matcher.group(i + 1)).byteValue();
699             return result;
700         }
701         else
702         {
703             Matcher ipv6Matcher = IPv6_PATTERN.matcher(address);
704             if (ipv6Matcher.matches())
705             {
706                 byte[] result = new byte[16];
707                 for (int i = 0; i < result.length; i += 2)
708                 {
709                     int word = Integer.valueOf(ipv6Matcher.group(i / 2 + 1), 16);
710                     result[i] = (byte)((word & 0xFF00) >>> 8);
711                     result[i + 1] = (byte)(word & 0xFF);
712                 }
713                 return result;
714             }
715         }
716         return null;
717     }
718 
719     private byte[] prefixToBytes(int prefix, int length)
720     {
721         byte[] result = new byte[length];
722         int index = 0;
723         while (prefix / 8 > 0)
724         {
725             result[index] = -1;
726             prefix -= 8;
727             ++index;
728         }
729 
730         if (index == result.length)
731             return result;
732 
733         // Sets the _prefix_ most significant bits to 1
734         result[index] = (byte)~((1 << (8 - prefix)) - 1);
735         return result;
736     }
737 
738     public void destroy()
739     {
740         LOG.debug("Destroy {}",this);
741         stopScheduler();
742         _rateTrackers.clear();
743         _whitelist.clear();
744     }
745 
746     protected void stopScheduler()
747     {
748         try
749         {
750             _scheduler.stop();
751         }
752         catch (Exception x)
753         {
754             LOG.ignore(x);
755         }
756     }
757 
758     /**
759      * Returns the user id, used to track this connection.
760      * This SHOULD be overridden by subclasses.
761      *
762      * @param request the current request
763      * @return a unique user id, if logged in; otherwise null.
764      */
765     protected String extractUserId(ServletRequest request)
766     {
767         return null;
768     }
769 
770     /**
771      * Get maximum number of requests from a connection per
772      * second. Requests in excess of this are first delayed,
773      * then throttled.
774      *
775      * @return maximum number of requests
776      */
777     @ManagedAttribute("maximum number of requests allowed from a connection per second")
778     public int getMaxRequestsPerSec()
779     {
780         return _maxRequestsPerSec;
781     }
782 
783     /**
784      * Get maximum number of requests from a connection per
785      * second. Requests in excess of this are first delayed,
786      * then throttled.
787      *
788      * @param value maximum number of requests
789      */
790     public void setMaxRequestsPerSec(int value)
791     {
792         _maxRequestsPerSec = value;
793     }
794 
795     /**
796      * Get delay (in milliseconds) that is applied to all requests
797      * over the rate limit, before they are considered at all.
798      * @return the delay in milliseconds
799      */
800     @ManagedAttribute("delay applied to all requests over the rate limit (in ms)")
801     public long getDelayMs()
802     {
803         return _delayMs;
804     }
805 
806     /**
807      * Set delay (in milliseconds) that is applied to all requests
808      * over the rate limit, before they are considered at all.
809      *
810      * @param value delay (in milliseconds), 0 - no delay, -1 - reject request
811      */
812     public void setDelayMs(long value)
813     {
814         _delayMs = value;
815     }
816 
817     /**
818      * Get maximum amount of time (in milliseconds) the filter will
819      * blocking wait for the throttle semaphore.
820      *
821      * @return maximum wait time
822      */
823     @ManagedAttribute("maximum time the filter will block waiting throttled connections, (0 for no delay, -1 to reject requests)")
824     public long getMaxWaitMs()
825     {
826         return _maxWaitMs;
827     }
828 
829     /**
830      * Set maximum amount of time (in milliseconds) the filter will
831      * blocking wait for the throttle semaphore.
832      *
833      * @param value maximum wait time
834      */
835     public void setMaxWaitMs(long value)
836     {
837         _maxWaitMs = value;
838     }
839 
840     /**
841      * Get number of requests over the rate limit able to be
842      * considered at once.
843      *
844      * @return number of requests
845      */
846     @ManagedAttribute("number of requests over rate limit")
847     public int getThrottledRequests()
848     {
849         return _throttledRequests;
850     }
851 
852     /**
853      * Set number of requests over the rate limit able to be
854      * considered at once.
855      *
856      * @param value number of requests
857      */
858     public void setThrottledRequests(int value)
859     {
860         int permits = _passes == null ? 0 : _passes.availablePermits();
861         _passes = new Semaphore((value - _throttledRequests + permits), true);
862         _throttledRequests = value;
863     }
864 
865     /**
866      * Get amount of time (in milliseconds) to async wait for semaphore.
867      *
868      * @return wait time
869      */
870     @ManagedAttribute("amount of time to async wait for semaphore")
871     public long getThrottleMs()
872     {
873         return _throttleMs;
874     }
875 
876     /**
877      * Set amount of time (in milliseconds) to async wait for semaphore.
878      *
879      * @param value wait time
880      */
881     public void setThrottleMs(long value)
882     {
883         _throttleMs = value;
884     }
885 
886     /**
887      * Get maximum amount of time (in milliseconds) to allow
888      * the request to process.
889      *
890      * @return maximum processing time
891      */
892     @ManagedAttribute("maximum time to allow requests to process (in ms)")
893     public long getMaxRequestMs()
894     {
895         return _maxRequestMs;
896     }
897 
898     /**
899      * Set maximum amount of time (in milliseconds) to allow
900      * the request to process.
901      *
902      * @param value maximum processing time
903      */
904     public void setMaxRequestMs(long value)
905     {
906         _maxRequestMs = value;
907     }
908 
909     /**
910      * Get maximum amount of time (in milliseconds) to keep track
911      * of request rates for a connection, before deciding that
912      * the user has gone away, and discarding it.
913      *
914      * @return maximum tracking time
915      */
916     @ManagedAttribute("maximum time to track of request rates for connection before discarding")
917     public long getMaxIdleTrackerMs()
918     {
919         return _maxIdleTrackerMs;
920     }
921 
922     /**
923      * Set maximum amount of time (in milliseconds) to keep track
924      * of request rates for a connection, before deciding that
925      * the user has gone away, and discarding it.
926      *
927      * @param value maximum tracking time
928      */
929     public void setMaxIdleTrackerMs(long value)
930     {
931         _maxIdleTrackerMs = value;
932     }
933 
934     /**
935      * Check flag to insert the DoSFilter headers into the response.
936      *
937      * @return value of the flag
938      */
939     @ManagedAttribute("inser DoSFilter headers in response")
940     public boolean isInsertHeaders()
941     {
942         return _insertHeaders;
943     }
944 
945     /**
946      * Set flag to insert the DoSFilter headers into the response.
947      *
948      * @param value value of the flag
949      */
950     public void setInsertHeaders(boolean value)
951     {
952         _insertHeaders = value;
953     }
954 
955     /**
956      * Get flag to have usage rate tracked by session if a session exists.
957      *
958      * @return value of the flag
959      */
960     @ManagedAttribute("usage rate is tracked by session if one exists")
961     public boolean isTrackSessions()
962     {
963         return _trackSessions;
964     }
965 
966     /**
967      * Set flag to have usage rate tracked by session if a session exists.
968      *
969      * @param value value of the flag
970      */
971     public void setTrackSessions(boolean value)
972     {
973         _trackSessions = value;
974     }
975 
976     /**
977      * Get flag to have usage rate tracked by IP+port (effectively connection)
978      * if session tracking is not used.
979      *
980      * @return value of the flag
981      */
982     @ManagedAttribute("usage rate is tracked by IP+port is session tracking not used")
983     public boolean isRemotePort()
984     {
985         return _remotePort;
986     }
987 
988     /**
989      * Set flag to have usage rate tracked by IP+port (effectively connection)
990      * if session tracking is not used.
991      *
992      * @param value value of the flag
993      */
994     public void setRemotePort(boolean value)
995     {
996         _remotePort = value;
997     }
998 
999     /**
1000      * @return whether this filter is enabled
1001      */
1002     @ManagedAttribute("whether this filter is enabled")
1003     public boolean isEnabled()
1004     {
1005         return _enabled;
1006     }
1007 
1008     /**
1009      * @param enabled whether this filter is enabled
1010      */
1011     public void setEnabled(boolean enabled)
1012     {
1013         _enabled = enabled;
1014     }
1015 
1016     public int getTooManyCode()
1017     {
1018         return _tooManyCode;
1019     }
1020 
1021     public void setTooManyCode(int tooManyCode)
1022     {
1023         _tooManyCode = tooManyCode;
1024     }
1025 
1026     /**
1027      * Get a list of IP addresses that will not be rate limited.
1028      *
1029      * @return comma-separated whitelist
1030      */
1031     @ManagedAttribute("list of IPs that will not be rate limited")
1032     public String getWhitelist()
1033     {
1034         StringBuilder result = new StringBuilder();
1035         for (Iterator<String> iterator = _whitelist.iterator(); iterator.hasNext();)
1036         {
1037             String address = iterator.next();
1038             result.append(address);
1039             if (iterator.hasNext())
1040                 result.append(",");
1041         }
1042         return result.toString();
1043     }
1044 
1045     /**
1046      * Set a list of IP addresses that will not be rate limited.
1047      *
1048      * @param commaSeparatedList comma-separated whitelist
1049      */
1050     public void setWhitelist(String commaSeparatedList)
1051     {
1052         List<String> result = new ArrayList<>();
1053         for (String address : StringUtil.csvSplit(commaSeparatedList))
1054             addWhitelistAddress(result, address);
1055         clearWhitelist();
1056         _whitelist.addAll(result);
1057         LOG.debug("Whitelisted IP addresses: {}", result);
1058     }
1059 
1060     /**
1061      * Clears the list of whitelisted IP addresses
1062      */
1063     @ManagedOperation("clears the list of IP addresses that will not be rate limited")
1064     public void clearWhitelist()
1065     {
1066         _whitelist.clear();
1067     }
1068 
1069     /**
1070      * Adds the given IP address, either in the form of a dotted decimal notation A.B.C.D
1071      * or in the CIDR notation A.B.C.D/M, to the list of whitelisted IP addresses.
1072      *
1073      * @param address the address to add
1074      * @return whether the address was added to the list
1075      * @see #removeWhitelistAddress(String)
1076      */
1077     @ManagedOperation("adds an IP address that will not be rate limited")
1078     public boolean addWhitelistAddress(@Name("address") String address)
1079     {
1080         return addWhitelistAddress(_whitelist, address);
1081     }
1082 
1083     private boolean addWhitelistAddress(List<String> list, String address)
1084     {
1085         address = address.trim();
1086         return address.length() > 0 && list.add(address);
1087     }
1088 
1089     /**
1090      * Removes the given address from the list of whitelisted IP addresses.
1091      *
1092      * @param address the address to remove
1093      * @return whether the address was removed from the list
1094      * @see #addWhitelistAddress(String)
1095      */
1096     @ManagedOperation("removes an IP address that will not be rate limited")
1097     public boolean removeWhitelistAddress(@Name("address") String address)
1098     {
1099         return _whitelist.remove(address);
1100     }
1101 
1102     /**
1103      * A RateTracker is associated with a connection, and stores request rate
1104      * data.
1105      */
1106     class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable
1107     {
1108         private static final long serialVersionUID = 3534663738034577872L;
1109 
1110         protected final String _id;
1111         protected final int _type;
1112         protected final long[] _timestamps;
1113         protected int _next;
1114 
1115         public RateTracker(String id, int type, int maxRequestsPerSecond)
1116         {
1117             _id = id;
1118             _type = type;
1119             _timestamps = new long[maxRequestsPerSecond];
1120             _next = 0;
1121         }
1122 
1123         /**
1124          * @param now the time now (in milliseconds)
1125          * @return the current calculated request rate over the last second
1126          */
1127         public boolean isRateExceeded(long now)
1128         {
1129             final long last;
1130             synchronized (this)
1131             {
1132                 last = _timestamps[_next];
1133                 _timestamps[_next] = now;
1134                 _next = (_next + 1) % _timestamps.length;
1135             }
1136 
1137             return last != 0 && (now - last) < 1000L;
1138         }
1139 
1140         public String getId()
1141         {
1142             return _id;
1143         }
1144 
1145         public int getType()
1146         {
1147             return _type;
1148         }
1149 
1150         public void valueBound(HttpSessionBindingEvent event)
1151         {
1152             if (LOG.isDebugEnabled())
1153                 LOG.debug("Value bound: {}", getId());
1154         }
1155 
1156         public void valueUnbound(HttpSessionBindingEvent event)
1157         {
1158             //take the tracker out of the list of trackers
1159             _rateTrackers.remove(_id);
1160             if (LOG.isDebugEnabled())
1161                 LOG.debug("Tracker removed: {}", getId());
1162         }
1163 
1164         public void sessionWillPassivate(HttpSessionEvent se)
1165         {
1166             //take the tracker of the list of trackers (if its still there)
1167             _rateTrackers.remove(_id);
1168         }
1169 
1170         public void sessionDidActivate(HttpSessionEvent se)
1171         {
1172             RateTracker tracker = (RateTracker)se.getSession().getAttribute(__TRACKER);
1173             if (tracker!=null)
1174                 _rateTrackers.put(tracker.getId(),tracker);
1175         }
1176 
1177         @Override
1178         public void run()
1179         {
1180             int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
1181             long last = _timestamps[latestIndex];
1182             boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L;
1183 
1184             if (hasRecentRequest)
1185                 _scheduler.schedule(this, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
1186             else
1187                 _rateTrackers.remove(_id);
1188         }
1189 
1190         @Override
1191         public String toString()
1192         {
1193             return "RateTracker/" + _id + "/" + _type;
1194         }
1195     }
1196 
1197     class FixedRateTracker extends RateTracker
1198     {
1199         public FixedRateTracker(String id, int type, int numRecentRequestsTracked)
1200         {
1201             super(id, type, numRecentRequestsTracked);
1202         }
1203 
1204         @Override
1205         public boolean isRateExceeded(long now)
1206         {
1207             // rate limit is never exceeded, but we keep track of the request timestamps
1208             // so that we know whether there was recent activity on this tracker
1209             // and whether it should be expired
1210             synchronized (this)
1211             {
1212                 _timestamps[_next] = now;
1213                 _next = (_next + 1) % _timestamps.length;
1214             }
1215 
1216             return false;
1217         }
1218 
1219         @Override
1220         public String toString()
1221         {
1222             return "Fixed" + super.toString();
1223         }
1224     }
1225 
1226     private class DoSTimeoutAsyncListener implements AsyncListener
1227     {
1228         @Override
1229         public void onStartAsync(AsyncEvent event) throws IOException
1230         {
1231         }
1232 
1233         @Override
1234         public void onComplete(AsyncEvent event) throws IOException
1235         {
1236         }
1237 
1238         @Override
1239         public void onTimeout(AsyncEvent event) throws IOException
1240         {
1241             event.getAsyncContext().dispatch();
1242         }
1243 
1244         @Override
1245         public void onError(AsyncEvent event) throws IOException
1246         {
1247         }
1248     }
1249 
1250     private class DoSAsyncListener extends DoSTimeoutAsyncListener
1251     {
1252         private final int priority;
1253 
1254         public DoSAsyncListener(int priority)
1255         {
1256             this.priority = priority;
1257         }
1258 
1259         @Override
1260         public void onTimeout(AsyncEvent event) throws IOException
1261         {
1262             _queues[priority].remove(event.getAsyncContext());
1263             super.onTimeout(event);
1264         }
1265     }
1266 }