View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses.
12  // ========================================================================
13  
14  package org.eclipse.jetty.servlets;
15  
16  import java.io.IOException;
17  import java.util.HashSet;
18  import java.util.Queue;
19  import java.util.StringTokenizer;
20  import java.util.concurrent.ConcurrentHashMap;
21  import java.util.concurrent.ConcurrentLinkedQueue;
22  import java.util.concurrent.Semaphore;
23  import java.util.concurrent.TimeUnit;
24  import javax.servlet.Filter;
25  import javax.servlet.FilterChain;
26  import javax.servlet.FilterConfig;
27  import javax.servlet.ServletContext;
28  import javax.servlet.ServletException;
29  import javax.servlet.ServletRequest;
30  import javax.servlet.ServletResponse;
31  import javax.servlet.http.HttpServletRequest;
32  import javax.servlet.http.HttpServletResponse;
33  import javax.servlet.http.HttpSession;
34  import javax.servlet.http.HttpSessionBindingEvent;
35  import javax.servlet.http.HttpSessionBindingListener;
36  
37  import org.eclipse.jetty.continuation.Continuation;
38  import org.eclipse.jetty.continuation.ContinuationListener;
39  import org.eclipse.jetty.continuation.ContinuationSupport;
40  import org.eclipse.jetty.server.handler.ContextHandler;
41  import org.eclipse.jetty.util.log.Log;
42  import org.eclipse.jetty.util.log.Logger;
43  import org.eclipse.jetty.util.thread.Timeout;
44  
45  /**
46   * Denial of Service filter
47   *
48   * <p>
49   * This filter is based on the {@link QoSFilter}. it is useful for limiting
50   * exposure to abuse from request flooding, whether malicious, or as a result of
51   * a misconfigured client.
52   * <p>
53   * The filter keeps track of the number of requests from a connection per
54   * second. If a limit is exceeded, the request is either rejected, delayed, or
55   * throttled.
56   * <p>
57   * When a request is throttled, it is placed in a priority queue. Priority is
58   * given first to authenticated users and users with an HttpSession, then
59   * connections which can be identified by their IP addresses. Connections with
60   * no way to identify them are given lowest priority.
61   * <p>
62   * The {@link #extractUserId(ServletRequest request)} function should be
63   * implemented, in order to uniquely identify authenticated users.
64   * <p>
65   * The following init parameters control the behavior of the filter:<dl>
66   *
67   * <dt>maxRequestsPerSec</dt>
68   *                      <dd>the maximum number of requests from a connection per
69   *                      second. Requests in excess of this are first delayed,
70   *                      then throttled.</dd>
71   *
72   * <dt>delayMs</dt>
73   *                      <dd>is the delay given to all requests over the rate limit,
74   *                      before they are considered at all. -1 means just reject request,
75   *                      0 means no delay, otherwise it is the delay.</dd>
76   *
77   * <dt>maxWaitMs</dt>
78   *                      <dd>how long to blocking wait for the throttle semaphore.</dd>
79   *
80   * <dt>throttledRequests</dt>
81   *                      <dd>is the number of requests over the rate limit able to be
82   *                      considered at once.</dd>
83   *
84   * <dt>throttleMs</dt>
85   *                      <dd>how long to async wait for semaphore.</dd>
86   *
87   * <dt>maxRequestMs</dt>
88   *                      <dd>how long to allow this request to run.</dd>
89   *
90   * <dt>maxIdleTrackerMs</dt>
91   *                      <dd>how long to keep track of request rates for a connection,
92   *                      before deciding that the user has gone away, and discarding it</dd>
93   *
94   * <dt>insertHeaders</dt>
95   *                      <dd>if true , insert the DoSFilter headers into the response. Defaults to true.</dd>
96   *
97   * <dt>trackSessions</dt>
98   *                      <dd>if true, usage rate is tracked by session if a session exists. Defaults to true.</dd>
99   *
100  * <dt>remotePort</dt>
101  *                      <dd>if true and session tracking is not used, then rate is tracked by IP+port (effectively connection). Defaults to false.</dd>
102  *
103  * <dt>ipWhitelist</dt>
104  *                      <dd>a comma-separated list of IP addresses that will not be rate limited</dd>
105  * 
106  * <dt>managedAttr</dt>
107  *                      <dd>if set to true, then this servlet is set as a {@link ServletContext} attribute with the 
108  * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
109  * manage the configuration of the filter.</dd>
110  * </dl>
111  * </p>
112  */
113 
114 public class DoSFilter implements Filter
115 {
116     private static final Logger LOG = Log.getLogger(DoSFilter.class);
117 
118     final static String __TRACKER = "DoSFilter.Tracker";
119     final static String __THROTTLED = "DoSFilter.Throttled";
120 
121     final static int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
122     final static int __DEFAULT_DELAY_MS = 100;
123     final static int __DEFAULT_THROTTLE = 5;
124     final static int __DEFAULT_WAIT_MS=50;
125     final static long __DEFAULT_THROTTLE_MS = 30000L;
126     final static long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM=30000L;
127     final static long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM=30000L;
128 
129     final static String MANAGED_ATTR_INIT_PARAM="managedAttr";
130     final static String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
131     final static String DELAY_MS_INIT_PARAM = "delayMs";
132     final static String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
133     final static String MAX_WAIT_INIT_PARAM="maxWaitMs";
134     final static String THROTTLE_MS_INIT_PARAM = "throttleMs";
135     final static String MAX_REQUEST_MS_INIT_PARAM="maxRequestMs";
136     final static String MAX_IDLE_TRACKER_MS_INIT_PARAM="maxIdleTrackerMs";
137     final static String INSERT_HEADERS_INIT_PARAM="insertHeaders";
138     final static String TRACK_SESSIONS_INIT_PARAM="trackSessions";
139     final static String REMOTE_PORT_INIT_PARAM="remotePort";
140     final static String IP_WHITELIST_INIT_PARAM="ipWhitelist";
141 
142     final static int USER_AUTH = 2;
143     final static int USER_SESSION = 2;
144     final static int USER_IP = 1;
145     final static int USER_UNKNOWN = 0;
146 
147     ServletContext _context;
148 
149     protected String _name;
150     protected long _delayMs;
151     protected long _throttleMs;
152     protected long _maxWaitMs;
153     protected long _maxRequestMs;
154     protected long _maxIdleTrackerMs;
155     protected boolean _insertHeaders;
156     protected boolean _trackSessions;
157     protected boolean _remotePort;
158     protected int _throttledRequests;
159     protected Semaphore _passes;
160     protected Queue<Continuation>[] _queue;
161     protected ContinuationListener[] _listener;
162 
163     protected int _maxRequestsPerSec;
164     protected final ConcurrentHashMap<String, RateTracker> _rateTrackers=new ConcurrentHashMap<String, RateTracker>();
165     protected String _whitelistStr;
166     private final HashSet<String> _whitelist = new HashSet<String>();
167 
168     private final Timeout _requestTimeoutQ = new Timeout();
169     private final Timeout _trackerTimeoutQ = new Timeout();
170 
171     private Thread _timerThread;
172     private volatile boolean _running;
173 
174     public void init(FilterConfig filterConfig)
175     {
176         _context = filterConfig.getServletContext();
177 
178         _queue = new Queue[getMaxPriority() + 1];
179         _listener = new ContinuationListener[getMaxPriority() + 1];
180         for (int p = 0; p < _queue.length; p++)
181         {
182             _queue[p] = new ConcurrentLinkedQueue<Continuation>();
183 
184             final int priority=p;
185             _listener[p] = new ContinuationListener()
186             {
187                 public void onComplete(Continuation continuation)
188                 {
189                 }
190 
191                 public void onTimeout(Continuation continuation)
192                 {
193                     _queue[priority].remove(continuation);
194                 }
195             };
196         }
197 
198         _rateTrackers.clear();
199 
200         int baseRateLimit = __DEFAULT_MAX_REQUESTS_PER_SEC;
201         if (filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM) != null)
202             baseRateLimit = Integer.parseInt(filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM));
203         _maxRequestsPerSec = baseRateLimit;
204 
205         long delay = __DEFAULT_DELAY_MS;
206         if (filterConfig.getInitParameter(DELAY_MS_INIT_PARAM) != null)
207             delay = Integer.parseInt(filterConfig.getInitParameter(DELAY_MS_INIT_PARAM));
208         _delayMs = delay;
209 
210         int throttledRequests = __DEFAULT_THROTTLE;
211         if (filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM) != null)
212             throttledRequests = Integer.parseInt(filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM));
213         _passes = new Semaphore(throttledRequests,true);
214         _throttledRequests = throttledRequests;
215 
216         long wait = __DEFAULT_WAIT_MS;
217         if (filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM) != null)
218             wait = Integer.parseInt(filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM));
219         _maxWaitMs = wait;
220 
221         long suspend = __DEFAULT_THROTTLE_MS;
222         if (filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM) != null)
223             suspend = Integer.parseInt(filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM));
224         _throttleMs = suspend;
225 
226         long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
227         if (filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM) != null )
228             maxRequestMs = Long.parseLong(filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM));
229         _maxRequestMs = maxRequestMs;
230 
231         long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
232         if (filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM) != null )
233             maxIdleTrackerMs = Long.parseLong(filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM));
234         _maxIdleTrackerMs = maxIdleTrackerMs;
235 
236         _whitelistStr = "";
237         if (filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM) !=null )
238             _whitelistStr = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
239         initWhitelist();
240 
241         String tmp = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
242         _insertHeaders = tmp==null || Boolean.parseBoolean(tmp);
243 
244         tmp = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
245         _trackSessions = tmp==null || Boolean.parseBoolean(tmp);
246 
247         tmp = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
248         _remotePort = tmp!=null&& Boolean.parseBoolean(tmp);
249 
250         _requestTimeoutQ.setNow();
251         _requestTimeoutQ.setDuration(_maxRequestMs);
252 
253         _trackerTimeoutQ.setNow();
254         _trackerTimeoutQ.setDuration(_maxIdleTrackerMs);
255 
256         _running=true;
257         _timerThread = (new Thread()
258         {
259             public void run()
260             {
261                 try
262                 {
263                     while (_running)
264                     {
265                         long now;
266                         synchronized (_requestTimeoutQ)
267                         {
268                             now = _requestTimeoutQ.setNow();
269                             _requestTimeoutQ.tick();
270                         }
271                         synchronized (_trackerTimeoutQ)
272                         {
273                             _trackerTimeoutQ.setNow(now);
274                             _trackerTimeoutQ.tick();
275                         }
276                         try
277                         {
278                             Thread.sleep(100);
279                         }
280                         catch (InterruptedException e)
281                         {
282                             LOG.ignore(e);
283                         }
284                     }
285                 }
286                 finally
287                 {
288                     LOG.info("DoSFilter timer exited");
289                 }
290             }
291         });
292         _timerThread.start();
293 
294         if (_context!=null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
295             _context.setAttribute(filterConfig.getFilterName(),this);
296     }
297 
298 
299     public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterchain) throws IOException, ServletException
300     {
301         final HttpServletRequest srequest = (HttpServletRequest)request;
302         final HttpServletResponse sresponse = (HttpServletResponse)response;
303 
304         final long now=_requestTimeoutQ.getNow();
305 
306         // Look for the rate tracker for this request
307         RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
308 
309         if (tracker==null)
310         {
311             // This is the first time we have seen this request.
312 
313             // get a rate tracker associated with this request, and record one hit
314             tracker = getRateTracker(request);
315 
316             // Calculate the rate and check it is over the allowed limit
317             final boolean overRateLimit = tracker.isRateExceeded(now);
318 
319             // pass it through if  we are not currently over the rate limit
320             if (!overRateLimit)
321             {
322                 doFilterChain(filterchain,srequest,sresponse);
323                 return;
324             }
325 
326             // We are over the limit.
327             LOG.warn("DOS ALERT: ip="+srequest.getRemoteAddr()+",session="+srequest.getRequestedSessionId()+",user="+srequest.getUserPrincipal());
328 
329             // So either reject it, delay it or throttle it
330             switch((int)_delayMs)
331             {
332                 case -1:
333                 {
334                     // Reject this request
335                     if (_insertHeaders)
336                         ((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
337                     
338                     ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
339                     return;
340                 }
341                 case 0:
342                 {
343                     // fall through to throttle code 
344                     request.setAttribute(__TRACKER,tracker);
345                     break;
346                 }
347                 default:
348                 { 
349                     // insert a delay before throttling the request
350                     if (_insertHeaders)
351                         ((HttpServletResponse)response).addHeader("DoSFilter","delayed");
352                     Continuation continuation = ContinuationSupport.getContinuation(request);
353                     request.setAttribute(__TRACKER,tracker);
354                     if (_delayMs > 0)
355                         continuation.setTimeout(_delayMs);
356                     continuation.addContinuationListener(new ContinuationListener() 
357                     {
358 
359                         public void onComplete(Continuation continuation)
360                         {
361                         }
362 
363                         public void onTimeout(Continuation continuation)
364                         {
365                         }
366                     });
367                     continuation.suspend();
368                     return;
369                 }
370             }
371         }
372         // Throttle the request
373         boolean accepted = false;
374         try
375         {
376             // check if we can afford to accept another request at this time
377             accepted = _passes.tryAcquire(_maxWaitMs,TimeUnit.MILLISECONDS);
378 
379             if (!accepted)
380             {
381                 // we were not accepted, so either we suspend to wait,or if we were woken up we insist or we fail
382                 final Continuation continuation = ContinuationSupport.getContinuation(request);
383 
384                 Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
385                 if (throttled!=Boolean.TRUE && _throttleMs>0)
386                 {
387                     int priority = getPriority(request,tracker);
388                     request.setAttribute(__THROTTLED,Boolean.TRUE);
389                     if (_insertHeaders)
390                         ((HttpServletResponse)response).addHeader("DoSFilter","throttled");
391                     if (_throttleMs > 0)
392                         continuation.setTimeout(_throttleMs);
393                     continuation.suspend();
394 
395                     continuation.addContinuationListener(_listener[priority]);
396                     _queue[priority].add(continuation);
397                     return;
398                 }
399                 // else were we resumed?
400                 else if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE)
401                 {
402                     // we were resumed and somebody stole our pass, so we wait for the next one.
403                     _passes.acquire();
404                     accepted = true;
405                 }
406             }
407 
408             // if we were accepted (either immediately or after throttle)
409             if (accepted)
410                 // call the chain
411                 doFilterChain(filterchain,srequest,sresponse);
412             else
413             {
414                 // fail the request
415                 if (_insertHeaders)
416                     ((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
417                 ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
418             }
419         }
420         catch (InterruptedException e)
421         {
422             _context.log("DoS",e);
423             ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
424         }
425         catch (Exception e)
426         {
427             e.printStackTrace();
428         }
429         finally
430         {
431             if (accepted)
432             {
433                 // wake up the next highest priority request.
434                 for (int p = _queue.length; p-- > 0;)
435                 {
436                     Continuation continuation = _queue[p].poll();
437                     if (continuation != null && continuation.isSuspended())
438                     {
439                         continuation.resume();
440                         break;
441                     }
442                 }
443                 _passes.release();
444             }
445         }
446     }
447 
448     /**
449      * @param chain
450      * @param request
451      * @param response
452      * @throws IOException
453      * @throws ServletException
454      */
455     protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response)
456         throws IOException, ServletException
457     {
458         final Thread thread=Thread.currentThread();
459 
460         final Timeout.Task requestTimeout = new Timeout.Task()
461         {
462             public void expired()
463             {
464                 closeConnection(request, response, thread);
465             }
466         };
467 
468         try
469         {
470             synchronized (_requestTimeoutQ)
471             {
472                 _requestTimeoutQ.schedule(requestTimeout);
473             }
474             chain.doFilter(request,response);
475         }
476         finally
477         {
478             synchronized (_requestTimeoutQ)
479             {
480                 requestTimeout.cancel();
481             }
482         }
483     }
484 
485     /**
486      * Takes drastic measures to return this response and stop this thread.
487      * Due to the way the connection is interrupted, may return mixed up headers.
488      * @param request current request
489      * @param response current response, which must be stopped
490      * @param thread the handling thread
491      */
492     protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
493     {
494         // take drastic measures to return this response and stop this thread.
495         if( !response.isCommitted() )
496         {
497             response.setHeader("Connection", "close");
498         }
499         try
500         {
501             try
502             {
503                 response.getWriter().close();
504             }
505             catch (IllegalStateException e)
506             {
507                 response.getOutputStream().close();
508             }
509         }
510         catch (IOException e)
511         {
512             LOG.warn(e);
513         }
514 
515         // interrupt the handling thread
516         thread.interrupt();
517     }
518 
519     /**
520      * Get priority for this request, based on user type
521      *
522      * @param request
523      * @param tracker
524      * @return priority
525      */
526     protected int getPriority(ServletRequest request, RateTracker tracker)
527     {
528         if (extractUserId(request)!=null)
529             return USER_AUTH;
530         if (tracker!=null)
531             return tracker.getType();
532         return USER_UNKNOWN;
533     }
534 
535     /**
536      * @return the maximum priority that we can assign to a request
537      */
538     protected int getMaxPriority()
539     {
540         return USER_AUTH;
541     }
542 
543     /**
544      * Return a request rate tracker associated with this connection; keeps
545      * track of this connection's request rate. If this is not the first request
546      * from this connection, return the existing object with the stored stats.
547      * If it is the first request, then create a new request tracker.
548      *
549      * Assumes that each connection has an identifying characteristic, and goes
550      * through them in order, taking the first that matches: user id (logged
551      * in), session id, client IP address. Unidentifiable connections are lumped
552      * into one.
553      *
554      * When a session expires, its rate tracker is automatically deleted.
555      *
556      * @param request
557      * @return the request rate tracker for the current connection
558      */
559     public RateTracker getRateTracker(ServletRequest request)
560     {
561         HttpServletRequest srequest = (HttpServletRequest)request;
562         HttpSession session=srequest.getSession(false);
563 
564         String loadId = extractUserId(request);
565         final int type;
566         if (loadId != null)
567         {
568             type = USER_AUTH;
569         }
570         else
571         {
572             if (_trackSessions && session!=null && !session.isNew())
573             {
574                 loadId=session.getId();
575                 type = USER_SESSION;
576             }
577             else
578             {
579                 loadId = _remotePort?(request.getRemoteAddr()+request.getRemotePort()):request.getRemoteAddr();
580                 type = USER_IP;
581             }
582         }
583 
584         RateTracker tracker=_rateTrackers.get(loadId);
585 
586         if (tracker==null)
587         {
588             RateTracker t;
589             if (_whitelist.contains(request.getRemoteAddr()))
590             {
591                 t = new FixedRateTracker(loadId,type,_maxRequestsPerSec);
592             }
593             else
594             {
595                 t = new RateTracker(loadId,type,_maxRequestsPerSec);
596             }
597 
598             tracker=_rateTrackers.putIfAbsent(loadId,t);
599             if (tracker==null)
600                 tracker=t;
601 
602             if (type == USER_IP)
603             {
604                 // USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
605                 synchronized (_trackerTimeoutQ)
606                 {
607                     _trackerTimeoutQ.schedule(tracker);
608                 }
609             }
610             else if (session!=null)
611                 // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
612                 session.setAttribute(__TRACKER,tracker);
613         }
614 
615         return tracker;
616     }
617 
618     public void destroy()
619     {
620         _running=false;
621         _timerThread.interrupt();
622         synchronized (_requestTimeoutQ)
623         {
624             _requestTimeoutQ.cancelAll();
625         }
626         synchronized (_trackerTimeoutQ)
627         {
628             _trackerTimeoutQ.cancelAll();
629         }
630         _rateTrackers.clear();
631         _whitelist.clear();
632     }
633 
634     /**
635      * Returns the user id, used to track this connection.
636      * This SHOULD be overridden by subclasses.
637      *
638      * @param request
639      * @return a unique user id, if logged in; otherwise null.
640      */
641     protected String extractUserId(ServletRequest request)
642     {
643         return null;
644     }
645 
646     /* ------------------------------------------------------------ */
647     /**
648      * Initialize the IP address whitelist
649      */
650     protected void initWhitelist()
651     {
652         _whitelist.clear();
653         StringTokenizer tokenizer = new StringTokenizer(_whitelistStr, ",");
654         while (tokenizer.hasMoreTokens())
655             _whitelist.add(tokenizer.nextToken().trim());
656 
657         LOG.info("Whitelisted IP addresses: {}", _whitelist.toString());
658     }
659     
660     /* ------------------------------------------------------------ */
661     /** 
662      * Get maximum number of requests from a connection per
663      * second. Requests in excess of this are first delayed,
664      * then throttled.
665      * 
666      * @return maximum number of requests
667      */
668     public int getMaxRequestsPerSec()
669     {
670         return _maxRequestsPerSec;
671     }
672 
673     /* ------------------------------------------------------------ */
674     /** 
675      * Get maximum number of requests from a connection per
676      * second. Requests in excess of this are first delayed,
677      * then throttled.
678      * 
679      * @param value maximum number of requests
680      */
681     public void setMaxRequestsPerSec(int value)
682     {
683         _maxRequestsPerSec = value;
684     }
685     
686     /* ------------------------------------------------------------ */
687     /**
688      * Get delay (in milliseconds) that is applied to all requests 
689      * over the rate limit, before they are considered at all. 
690      */
691     public long getDelayMs()
692     {
693         return _delayMs;
694     }
695 
696     /* ------------------------------------------------------------ */
697     /**
698      * Set delay (in milliseconds) that is applied to all requests 
699      * over the rate limit, before they are considered at all. 
700      * 
701      * @param value delay (in milliseconds), 0 - no delay, -1 - reject request
702      */
703     public void setDelayMs(long value)
704     {
705         _delayMs = value;
706     }
707     
708     /* ------------------------------------------------------------ */
709     /**
710      * Get maximum amount of time (in milliseconds) the filter will
711      * blocking wait for the throttle semaphore.
712      * 
713      * @return maximum wait time
714      */
715     public long getMaxWaitMs()
716     {
717         return _maxWaitMs;
718     }
719 
720     /* ------------------------------------------------------------ */
721     /** 
722      * Set maximum amount of time (in milliseconds) the filter will
723      * blocking wait for the throttle semaphore.
724      * 
725      * @param value maximum wait time
726      */
727     public void setMaxWaitMs(long value)
728     {
729         _maxWaitMs = value;
730     }
731 
732     /* ------------------------------------------------------------ */
733     /**
734      * Get number of requests over the rate limit able to be
735      * considered at once.
736      * 
737      * @return number of requests
738      */
739     public int getThrottledRequests()
740     {
741         return _throttledRequests;
742     }
743 
744     /* ------------------------------------------------------------ */
745     /**
746      * Set number of requests over the rate limit able to be
747      * considered at once.
748      * 
749      * @param value number of requests
750      */
751     public void setThrottledRequests(int value)
752     {
753         _passes = new Semaphore((value-_throttledRequests+_passes.availablePermits()), true);
754         _throttledRequests = value;
755     }
756 
757     /* ------------------------------------------------------------ */
758     /**
759      * Get amount of time (in milliseconds) to async wait for semaphore.
760      * 
761      * @return wait time
762      */
763     public long getThrottleMs()
764     {
765         return _throttleMs;
766     }
767 
768     /* ------------------------------------------------------------ */
769     /**
770      * Set amount of time (in milliseconds) to async wait for semaphore.
771      * 
772      * @param value wait time
773      */
774     public void setThrottleMs(long value)
775     {
776         _throttleMs = value;
777     }
778 
779     /* ------------------------------------------------------------ */
780     /** 
781      * Get maximum amount of time (in milliseconds) to allow 
782      * the request to process.
783      * 
784      * @return maximum processing time
785      */
786     public long getMaxRequestMs()
787     {
788         return _maxRequestMs;
789     }
790 
791     /* ------------------------------------------------------------ */
792     /** 
793      * Set maximum amount of time (in milliseconds) to allow 
794      * the request to process.
795      * 
796      * @param value maximum processing time
797      */
798     public void setMaxRequestMs(long value)
799     {
800         _maxRequestMs = value;
801     }
802 
803     /* ------------------------------------------------------------ */
804     /**
805      * Get maximum amount of time (in milliseconds) to keep track
806      * of request rates for a connection, before deciding that 
807      * the user has gone away, and discarding it.
808      * 
809      * @return maximum tracking time
810      */
811     public long getMaxIdleTrackerMs()
812     {
813         return _maxIdleTrackerMs;
814     }
815 
816     /* ------------------------------------------------------------ */
817     /** 
818      * Set maximum amount of time (in milliseconds) to keep track
819      * of request rates for a connection, before deciding that 
820      * the user has gone away, and discarding it.
821      * 
822      * @param value maximum tracking time
823      */
824     public void setMaxIdleTrackerMs(long value)
825     {
826         _maxIdleTrackerMs = value;
827     }
828 
829     /* ------------------------------------------------------------ */
830     /** 
831      * Check flag to insert the DoSFilter headers into the response.
832      * 
833      * @return value of the flag
834      */
835     public boolean isInsertHeaders()
836     {
837         return _insertHeaders;
838     }
839 
840     /* ------------------------------------------------------------ */
841     /** 
842      * Set flag to insert the DoSFilter headers into the response.
843      * 
844      * @param value value of the flag
845      */
846     public void setInsertHeaders(boolean value)
847     {
848         _insertHeaders = value;
849     }
850 
851     /* ------------------------------------------------------------ */
852     /** 
853      * Get flag to have usage rate tracked by session if a session exists.
854      * 
855      * @return value of the flag
856      */
857     public boolean isTrackSessions()
858     {
859         return _trackSessions;
860     }
861 
862     /* ------------------------------------------------------------ */
863     /** 
864      * Set flag to have usage rate tracked by session if a session exists.
865      * @param value value of the flag
866      */
867     public void setTrackSessions(boolean value)
868     {
869         _trackSessions = value;
870     }
871 
872     /* ------------------------------------------------------------ */
873     /** 
874      * Get flag to have usage rate tracked by IP+port (effectively connection)
875      * if session tracking is not used.
876      * 
877      * @return value of the flag
878      */
879     public boolean isRemotePort()
880     {
881         return _remotePort;
882     }
883 
884 
885     /* ------------------------------------------------------------ */
886     /** 
887      * Set flag to have usage rate tracked by IP+port (effectively connection)
888      * if session tracking is not used.
889      * 
890      * @param value value of the flag
891      */
892     public void setRemotePort(boolean value)
893     {
894         _remotePort = value;
895     }
896 
897     /* ------------------------------------------------------------ */
898     /**
899      * Get a list of IP addresses that will not be rate limited.
900      * 
901      * @return comma-separated whitelist
902      */
903     public String getWhitelist()
904     {
905         return _whitelistStr;
906     }
907 
908 
909     /* ------------------------------------------------------------ */
910     /**
911      * Set a list of IP addresses that will not be rate limited.
912      * 
913      * @param value comma-separated whitelist
914      */
915     public void setWhitelist(String value)
916     {
917         _whitelistStr = value;
918         initWhitelist();
919     }
920 
921     /**
922      * A RateTracker is associated with a connection, and stores request rate
923      * data.
924      */
925     class RateTracker extends Timeout.Task implements HttpSessionBindingListener
926     {
927         protected final String _id;
928         protected final int _type;
929         protected final long[] _timestamps;
930         protected int _next;
931 
932         public RateTracker(String id, int type,int maxRequestsPerSecond)
933         {
934             _id = id;
935             _type = type;
936             _timestamps=new long[maxRequestsPerSecond];
937             _next=0;
938         }
939 
940         /**
941          * @return the current calculated request rate over the last second
942          */
943         public boolean isRateExceeded(long now)
944         {
945             final long last;
946             synchronized (this)
947             {
948                 last=_timestamps[_next];
949                 _timestamps[_next]=now;
950                 _next= (_next+1)%_timestamps.length;
951             }
952 
953             boolean exceeded=last!=0 && (now-last)<1000L;
954             return exceeded;
955         }
956 
957 
958         public String getId()
959         {
960             return _id;
961         }
962 
963         public int getType()
964         {
965             return _type;
966         }
967 
968 
969         public void valueBound(HttpSessionBindingEvent event)
970         {
971         }
972 
973         public void valueUnbound(HttpSessionBindingEvent event)
974         {
975             _rateTrackers.remove(_id);
976         }
977 
978         public void expired()
979         {
980             long now = _trackerTimeoutQ.getNow();
981             int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length;
982             long last=_timestamps[latestIndex];
983             boolean hasRecentRequest = last != 0 && (now-last)<1000L;
984 
985             if (hasRecentRequest)
986                 reschedule();
987             else
988                 _rateTrackers.remove(_id);
989         }
990 
991         @Override
992         public String toString()
993         {
994             return "RateTracker/"+_id+"/"+_type;
995         }
996     }
997 
998     class FixedRateTracker extends RateTracker
999     {
1000         public FixedRateTracker(String id, int type, int numRecentRequestsTracked)
1001         {
1002             super(id,type,numRecentRequestsTracked);
1003         }
1004 
1005         @Override
1006         public boolean isRateExceeded(long now)
1007         {
1008             // rate limit is never exceeded, but we keep track of the request timestamps
1009             // so that we know whether there was recent activity on this tracker
1010             // and whether it should be expired
1011             synchronized (this)
1012             {
1013                 _timestamps[_next]=now;
1014                 _next= (_next+1)%_timestamps.length;
1015             }
1016 
1017             return false;
1018         }
1019 
1020         @Override
1021         public String toString()
1022         {
1023             return "Fixed"+super.toString();
1024         }
1025     }
1026 }