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