View Javadoc

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