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                     ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
338                     return;
339                 }
340                 case 0:
341                 {
342                     // fall through to throttle code
343                     request.setAttribute(__TRACKER,tracker);
344                     break;
345                 }
346                 default:
347                 {
348                     // insert a delay before throttling the request
349                     if (_insertHeaders)
350                         ((HttpServletResponse)response).addHeader("DoSFilter","delayed");
351                     Continuation continuation = ContinuationSupport.getContinuation(request);
352                     request.setAttribute(__TRACKER,tracker);
353                     if (_delayMs > 0)
354                         continuation.setTimeout(_delayMs);
355                     continuation.suspend();
356                     return;
357                 }
358             }
359         }
360 
361         // Throttle the request
362         boolean accepted = false;
363         try
364         {
365             // check if we can afford to accept another request at this time
366             accepted = _passes.tryAcquire(_maxWaitMs,TimeUnit.MILLISECONDS);
367 
368             if (!accepted)
369             {
370                 // we were not accepted, so either we suspend to wait,or if we were woken up we insist or we fail
371                 final Continuation continuation = ContinuationSupport.getContinuation(request);
372 
373                 Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
374                 if (throttled!=Boolean.TRUE && _throttleMs>0)
375                 {
376                     int priority = getPriority(request,tracker);
377                     request.setAttribute(__THROTTLED,Boolean.TRUE);
378                     if (_insertHeaders)
379                         ((HttpServletResponse)response).addHeader("DoSFilter","throttled");
380                     if (_throttleMs > 0)
381                         continuation.setTimeout(_throttleMs);
382                     continuation.suspend();
383 
384                     continuation.addContinuationListener(_listener[priority]);
385                     _queue[priority].add(continuation);
386                     return;
387                 }
388                 // else were we resumed?
389                 else if (request.getAttribute("javax.servlet.resumed")==Boolean.TRUE)
390                 {
391                     // we were resumed and somebody stole our pass, so we wait for the next one.
392                     _passes.acquire();
393                     accepted = true;
394                 }
395             }
396 
397             // if we were accepted (either immediately or after throttle)
398             if (accepted)
399                 // call the chain
400                 doFilterChain(filterchain,srequest,sresponse);
401             else
402             {
403                 // fail the request
404                 if (_insertHeaders)
405                     ((HttpServletResponse)response).addHeader("DoSFilter","unavailable");
406                 ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
407             }
408         }
409         catch (InterruptedException e)
410         {
411             _context.log("DoS",e);
412             ((HttpServletResponse)response).sendError(HttpServletResponse.SC_SERVICE_UNAVAILABLE);
413         }
414         finally
415         {
416             if (accepted)
417             {
418                 // wake up the next highest priority request.
419                 for (int p = _queue.length; p-- > 0;)
420                 {
421                     Continuation continuation = _queue[p].poll();
422                     if (continuation != null && continuation.isSuspended())
423                     {
424                         continuation.resume();
425                         break;
426                     }
427                 }
428                 _passes.release();
429             }
430         }
431     }
432 
433     /**
434      * @param chain
435      * @param request
436      * @param response
437      * @throws IOException
438      * @throws ServletException
439      */
440     protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response)
441         throws IOException, ServletException
442     {
443         final Thread thread=Thread.currentThread();
444 
445         final Timeout.Task requestTimeout = new Timeout.Task()
446         {
447             public void expired()
448             {
449                 closeConnection(request, response, thread);
450             }
451         };
452 
453         try
454         {
455             synchronized (_requestTimeoutQ)
456             {
457                 _requestTimeoutQ.schedule(requestTimeout);
458             }
459             chain.doFilter(request,response);
460         }
461         finally
462         {
463             synchronized (_requestTimeoutQ)
464             {
465                 requestTimeout.cancel();
466             }
467         }
468     }
469 
470     /**
471      * Takes drastic measures to return this response and stop this thread.
472      * Due to the way the connection is interrupted, may return mixed up headers.
473      * @param request current request
474      * @param response current response, which must be stopped
475      * @param thread the handling thread
476      */
477     protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
478     {
479         // take drastic measures to return this response and stop this thread.
480         if( !response.isCommitted() )
481         {
482             response.setHeader("Connection", "close");
483         }
484         try
485         {
486             try
487             {
488                 response.getWriter().close();
489             }
490             catch (IllegalStateException e)
491             {
492                 response.getOutputStream().close();
493             }
494         }
495         catch (IOException e)
496         {
497             LOG.warn(e);
498         }
499 
500         // interrupt the handling thread
501         thread.interrupt();
502     }
503 
504     /**
505      * Get priority for this request, based on user type
506      *
507      * @param request
508      * @param tracker
509      * @return priority
510      */
511     protected int getPriority(ServletRequest request, RateTracker tracker)
512     {
513         if (extractUserId(request)!=null)
514             return USER_AUTH;
515         if (tracker!=null)
516             return tracker.getType();
517         return USER_UNKNOWN;
518     }
519 
520     /**
521      * @return the maximum priority that we can assign to a request
522      */
523     protected int getMaxPriority()
524     {
525         return USER_AUTH;
526     }
527 
528     /**
529      * Return a request rate tracker associated with this connection; keeps
530      * track of this connection's request rate. If this is not the first request
531      * from this connection, return the existing object with the stored stats.
532      * If it is the first request, then create a new request tracker.
533      *
534      * Assumes that each connection has an identifying characteristic, and goes
535      * through them in order, taking the first that matches: user id (logged
536      * in), session id, client IP address. Unidentifiable connections are lumped
537      * into one.
538      *
539      * When a session expires, its rate tracker is automatically deleted.
540      *
541      * @param request
542      * @return the request rate tracker for the current connection
543      */
544     public RateTracker getRateTracker(ServletRequest request)
545     {
546         HttpServletRequest srequest = (HttpServletRequest)request;
547         HttpSession session=srequest.getSession(false);
548 
549         String loadId = extractUserId(request);
550         final int type;
551         if (loadId != null)
552         {
553             type = USER_AUTH;
554         }
555         else
556         {
557             if (_trackSessions && session!=null && !session.isNew())
558             {
559                 loadId=session.getId();
560                 type = USER_SESSION;
561             }
562             else
563             {
564                 loadId = _remotePort?(request.getRemoteAddr()+request.getRemotePort()):request.getRemoteAddr();
565                 type = USER_IP;
566             }
567         }
568 
569         RateTracker tracker=_rateTrackers.get(loadId);
570 
571         if (tracker==null)
572         {
573             RateTracker t;
574             if (_whitelist.contains(request.getRemoteAddr()))
575             {
576                 t = new FixedRateTracker(loadId,type,_maxRequestsPerSec);
577             }
578             else
579             {
580                 t = new RateTracker(loadId,type,_maxRequestsPerSec);
581             }
582 
583             tracker=_rateTrackers.putIfAbsent(loadId,t);
584             if (tracker==null)
585                 tracker=t;
586 
587             if (type == USER_IP)
588             {
589                 // USER_IP expiration from _rateTrackers is handled by the _trackerTimeoutQ
590                 synchronized (_trackerTimeoutQ)
591                 {
592                     _trackerTimeoutQ.schedule(tracker);
593                 }
594             }
595             else if (session!=null)
596                 // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
597                 session.setAttribute(__TRACKER,tracker);
598         }
599 
600         return tracker;
601     }
602 
603     public void destroy()
604     {
605         _running=false;
606         _timerThread.interrupt();
607         synchronized (_requestTimeoutQ)
608         {
609             _requestTimeoutQ.cancelAll();
610         }
611         synchronized (_trackerTimeoutQ)
612         {
613             _trackerTimeoutQ.cancelAll();
614         }
615         _rateTrackers.clear();
616         _whitelist.clear();
617     }
618 
619     /**
620      * Returns the user id, used to track this connection.
621      * This SHOULD be overridden by subclasses.
622      *
623      * @param request
624      * @return a unique user id, if logged in; otherwise null.
625      */
626     protected String extractUserId(ServletRequest request)
627     {
628         return null;
629     }
630 
631     /* ------------------------------------------------------------ */
632     /**
633      * Initialize the IP address whitelist
634      */
635     protected void initWhitelist()
636     {
637         _whitelist.clear();
638         StringTokenizer tokenizer = new StringTokenizer(_whitelistStr, ",");
639         while (tokenizer.hasMoreTokens())
640             _whitelist.add(tokenizer.nextToken().trim());
641 
642         LOG.info("Whitelisted IP addresses: {}", _whitelist.toString());
643     }
644     
645     /* ------------------------------------------------------------ */
646     /** 
647      * Get maximum number of requests from a connection per
648      * second. Requests in excess of this are first delayed,
649      * then throttled.
650      * 
651      * @return maximum number of requests
652      */
653     public int getMaxRequestsPerSec()
654     {
655         return _maxRequestsPerSec;
656     }
657 
658     /* ------------------------------------------------------------ */
659     /** 
660      * Get maximum number of requests from a connection per
661      * second. Requests in excess of this are first delayed,
662      * then throttled.
663      * 
664      * @param value maximum number of requests
665      */
666     public void setMaxRequestsPerSec(int value)
667     {
668         _maxRequestsPerSec = value;
669     }
670     
671     /* ------------------------------------------------------------ */
672     /**
673      * Get delay (in milliseconds) that is applied to all requests 
674      * over the rate limit, before they are considered at all. 
675      */
676     public long getDelayMs()
677     {
678         return _delayMs;
679     }
680 
681     /* ------------------------------------------------------------ */
682     /**
683      * Set delay (in milliseconds) that is applied to all requests 
684      * over the rate limit, before they are considered at all. 
685      * 
686      * @param value delay (in milliseconds), 0 - no delay, -1 - reject request
687      */
688     public void setDelayMs(long value)
689     {
690         _delayMs = value;
691     }
692     
693     /* ------------------------------------------------------------ */
694     /**
695      * Get maximum amount of time (in milliseconds) the filter will
696      * blocking wait for the throttle semaphore.
697      * 
698      * @return maximum wait time
699      */
700     public long getMaxWaitMs()
701     {
702         return _maxWaitMs;
703     }
704 
705     /* ------------------------------------------------------------ */
706     /** 
707      * Set maximum amount of time (in milliseconds) the filter will
708      * blocking wait for the throttle semaphore.
709      * 
710      * @param value maximum wait time
711      */
712     public void setMaxWaitMs(long value)
713     {
714         _maxWaitMs = value;
715     }
716 
717     /* ------------------------------------------------------------ */
718     /**
719      * Get number of requests over the rate limit able to be
720      * considered at once.
721      * 
722      * @return number of requests
723      */
724     public int getThrottledRequests()
725     {
726         return _throttledRequests;
727     }
728 
729     /* ------------------------------------------------------------ */
730     /**
731      * Set number of requests over the rate limit able to be
732      * considered at once.
733      * 
734      * @param value number of requests
735      */
736     public void setThrottledRequests(int value)
737     {
738         _passes = new Semaphore((value-_throttledRequests+_passes.availablePermits()), true);
739         _throttledRequests = value;
740     }
741 
742     /* ------------------------------------------------------------ */
743     /**
744      * Get amount of time (in milliseconds) to async wait for semaphore.
745      * 
746      * @return wait time
747      */
748     public long getThrottleMs()
749     {
750         return _throttleMs;
751     }
752 
753     /* ------------------------------------------------------------ */
754     /**
755      * Set amount of time (in milliseconds) to async wait for semaphore.
756      * 
757      * @param value wait time
758      */
759     public void setThrottleMs(long value)
760     {
761         _throttleMs = value;
762     }
763 
764     /* ------------------------------------------------------------ */
765     /** 
766      * Get maximum amount of time (in milliseconds) to allow 
767      * the request to process.
768      * 
769      * @return maximum processing time
770      */
771     public long getMaxRequestMs()
772     {
773         return _maxRequestMs;
774     }
775 
776     /* ------------------------------------------------------------ */
777     /** 
778      * Set maximum amount of time (in milliseconds) to allow 
779      * the request to process.
780      * 
781      * @param value maximum processing time
782      */
783     public void setMaxRequestMs(long value)
784     {
785         _maxRequestMs = value;
786     }
787 
788     /* ------------------------------------------------------------ */
789     /**
790      * Get maximum amount of time (in milliseconds) to keep track
791      * of request rates for a connection, before deciding that 
792      * the user has gone away, and discarding it.
793      * 
794      * @return maximum tracking time
795      */
796     public long getMaxIdleTrackerMs()
797     {
798         return _maxIdleTrackerMs;
799     }
800 
801     /* ------------------------------------------------------------ */
802     /** 
803      * Set maximum amount of time (in milliseconds) to keep track
804      * of request rates for a connection, before deciding that 
805      * the user has gone away, and discarding it.
806      * 
807      * @param value maximum tracking time
808      */
809     public void setMaxIdleTrackerMs(long value)
810     {
811         _maxIdleTrackerMs = value;
812     }
813 
814     /* ------------------------------------------------------------ */
815     /** 
816      * Check flag to insert the DoSFilter headers into the response.
817      * 
818      * @return value of the flag
819      */
820     public boolean isInsertHeaders()
821     {
822         return _insertHeaders;
823     }
824 
825     /* ------------------------------------------------------------ */
826     /** 
827      * Set flag to insert the DoSFilter headers into the response.
828      * 
829      * @param value value of the flag
830      */
831     public void setInsertHeaders(boolean value)
832     {
833         _insertHeaders = value;
834     }
835 
836     /* ------------------------------------------------------------ */
837     /** 
838      * Get flag to have usage rate tracked by session if a session exists.
839      * 
840      * @return value of the flag
841      */
842     public boolean isTrackSessions()
843     {
844         return _trackSessions;
845     }
846 
847     /* ------------------------------------------------------------ */
848     /** 
849      * Set flag to have usage rate tracked by session if a session exists.
850      * @param value value of the flag
851      */
852     public void setTrackSessions(boolean value)
853     {
854         _trackSessions = value;
855     }
856 
857     /* ------------------------------------------------------------ */
858     /** 
859      * Get flag to have usage rate tracked by IP+port (effectively connection)
860      * if session tracking is not used.
861      * 
862      * @return value of the flag
863      */
864     public boolean isRemotePort()
865     {
866         return _remotePort;
867     }
868 
869 
870     /* ------------------------------------------------------------ */
871     /** 
872      * Set flag to have usage rate tracked by IP+port (effectively connection)
873      * if session tracking is not used.
874      * 
875      * @param value value of the flag
876      */
877     public void setRemotePort(boolean value)
878     {
879         _remotePort = value;
880     }
881 
882     /* ------------------------------------------------------------ */
883     /**
884      * Get a list of IP addresses that will not be rate limited.
885      * 
886      * @return comma-separated whitelist
887      */
888     public String getWhitelist()
889     {
890         return _whitelistStr;
891     }
892 
893 
894     /* ------------------------------------------------------------ */
895     /**
896      * Set a list of IP addresses that will not be rate limited.
897      * 
898      * @param value comma-separated whitelist
899      */
900     public void setWhitelist(String value)
901     {
902         _whitelistStr = value;
903         initWhitelist();
904     }
905 
906     /**
907      * A RateTracker is associated with a connection, and stores request rate
908      * data.
909      */
910     class RateTracker extends Timeout.Task implements HttpSessionBindingListener
911     {
912         protected final String _id;
913         protected final int _type;
914         protected final long[] _timestamps;
915         protected int _next;
916 
917         public RateTracker(String id, int type,int maxRequestsPerSecond)
918         {
919             _id = id;
920             _type = type;
921             _timestamps=new long[maxRequestsPerSecond];
922             _next=0;
923         }
924 
925         /**
926          * @return the current calculated request rate over the last second
927          */
928         public boolean isRateExceeded(long now)
929         {
930             final long last;
931             synchronized (this)
932             {
933                 last=_timestamps[_next];
934                 _timestamps[_next]=now;
935                 _next= (_next+1)%_timestamps.length;
936             }
937 
938             boolean exceeded=last!=0 && (now-last)<1000L;
939             return exceeded;
940         }
941 
942 
943         public String getId()
944         {
945             return _id;
946         }
947 
948         public int getType()
949         {
950             return _type;
951         }
952 
953 
954         public void valueBound(HttpSessionBindingEvent event)
955         {
956         }
957 
958         public void valueUnbound(HttpSessionBindingEvent event)
959         {
960             _rateTrackers.remove(_id);
961         }
962 
963         public void expired()
964         {
965             long now = _trackerTimeoutQ.getNow();
966             int latestIndex = _next == 0 ? 3 : (_next - 1 ) % _timestamps.length;
967             long last=_timestamps[latestIndex];
968             boolean hasRecentRequest = last != 0 && (now-last)<1000L;
969 
970             if (hasRecentRequest)
971                 reschedule();
972             else
973                 _rateTrackers.remove(_id);
974         }
975 
976         @Override
977         public String toString()
978         {
979             return "RateTracker/"+_id+"/"+_type;
980         }
981     }
982 
983     class FixedRateTracker extends RateTracker
984     {
985         public FixedRateTracker(String id, int type, int numRecentRequestsTracked)
986         {
987             super(id,type,numRecentRequestsTracked);
988         }
989 
990         @Override
991         public boolean isRateExceeded(long now)
992         {
993             // rate limit is never exceeded, but we keep track of the request timestamps
994             // so that we know whether there was recent activity on this tracker
995             // and whether it should be expired
996             synchronized (this)
997             {
998                 _timestamps[_next]=now;
999                 _next= (_next+1)%_timestamps.length;
1000             }
1001 
1002             return false;
1003         }
1004 
1005         @Override
1006         public String toString()
1007         {
1008             return "Fixed"+super.toString();
1009         }
1010     }
1011 }