View Javadoc

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