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