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