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