View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.io.Serializable;
23  import java.util.ArrayList;
24  import java.util.Iterator;
25  import java.util.List;
26  import java.util.Queue;
27  import java.util.concurrent.ConcurrentHashMap;
28  import java.util.concurrent.ConcurrentLinkedQueue;
29  import java.util.concurrent.CopyOnWriteArrayList;
30  import java.util.concurrent.Semaphore;
31  import java.util.concurrent.TimeUnit;
32  import java.util.regex.Matcher;
33  import java.util.regex.Pattern;
34  
35  import javax.servlet.AsyncContext;
36  import javax.servlet.AsyncEvent;
37  import javax.servlet.AsyncListener;
38  import javax.servlet.DispatcherType;
39  import javax.servlet.Filter;
40  import javax.servlet.FilterChain;
41  import javax.servlet.FilterConfig;
42  import javax.servlet.ServletContext;
43  import javax.servlet.ServletException;
44  import javax.servlet.ServletRequest;
45  import javax.servlet.ServletResponse;
46  import javax.servlet.http.HttpServletRequest;
47  import javax.servlet.http.HttpServletResponse;
48  import javax.servlet.http.HttpSession;
49  import javax.servlet.http.HttpSessionActivationListener;
50  import javax.servlet.http.HttpSessionBindingEvent;
51  import javax.servlet.http.HttpSessionBindingListener;
52  import javax.servlet.http.HttpSessionEvent;
53  
54  import org.eclipse.jetty.server.handler.ContextHandler;
55  import org.eclipse.jetty.util.StringUtil;
56  import org.eclipse.jetty.util.annotation.ManagedAttribute;
57  import org.eclipse.jetty.util.annotation.ManagedObject;
58  import org.eclipse.jetty.util.annotation.ManagedOperation;
59  import org.eclipse.jetty.util.annotation.Name;
60  import org.eclipse.jetty.util.log.Log;
61  import org.eclipse.jetty.util.log.Logger;
62  import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
63  import org.eclipse.jetty.util.thread.Scheduler;
64  
65  /**
66   * Denial of Service filter
67   * <p>
68   * This filter is useful for limiting
69   * exposure to abuse from request flooding, whether malicious, or as a result of
70   * a misconfigured client.
71   * <p>
72   * The filter keeps track of the number of requests from a connection per
73   * second. If a limit is exceeded, the request is either rejected, delayed, or
74   * throttled.
75   * <p>
76   * When a request is throttled, it is placed in a priority queue. Priority is
77   * given first to authenticated users and users with an HttpSession, then
78   * connections which can be identified by their IP addresses. Connections with
79   * no way to identify them are given lowest priority.
80   * <p>
81   * The {@link #extractUserId(ServletRequest request)} function should be
82   * implemented, in order to uniquely identify authenticated users.
83   * <p>
84   * The following init parameters control the behavior of the filter:
85   * <dl>
86   * <dt>maxRequestsPerSec</dt>
87   * <dd>the maximum number of requests from a connection per
88   * second. Requests in excess of this are first delayed,
89   * then throttled.</dd>
90   * <dt>delayMs</dt>
91   * <dd>is the delay given to all requests over the rate limit,
92   * before they are considered at all. -1 means just reject request,
93   * 0 means no delay, otherwise it is the delay.</dd>
94   * <dt>maxWaitMs</dt>
95   * <dd>how long to blocking wait for the throttle semaphore.</dd>
96   * <dt>throttledRequests</dt>
97   * <dd>is the number of requests over the rate limit able to be
98   * considered at once.</dd>
99   * <dt>throttleMs</dt>
100  * <dd>how long to async wait for semaphore.</dd>
101  * <dt>maxRequestMs</dt>
102  * <dd>how long to allow this request to run.</dd>
103  * <dt>maxIdleTrackerMs</dt>
104  * <dd>how long to keep track of request rates for a connection,
105  * before deciding that the user has gone away, and discarding it</dd>
106  * <dt>insertHeaders</dt>
107  * <dd>if true , insert the DoSFilter headers into the response. Defaults to true.</dd>
108  * <dt>trackSessions</dt>
109  * <dd>if true, usage rate is tracked by session if a session exists. Defaults to true.</dd>
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  * <dt>ipWhitelist</dt>
113  * <dd>a comma-separated list of IP addresses that will not be rate limited</dd>
114  * <dt>managedAttr</dt>
115  * <dd>if set to true, then this servlet is set as a {@link ServletContext} attribute with the
116  * filter name as the attribute name.  This allows context external mechanism (eg JMX via {@link ContextHandler#MANAGED_ATTRIBUTES}) to
117  * manage the configuration of the filter.</dd>
118  * <dt>tooManyCode</dt>
119  * <dd>The status code to send if there are too many requests.  By default is 429 (too many requests), but 503 (Unavailable) is 
120  * another option</dd>
121  * </dl>
122  * <p>
123  * This filter should be configured for {@link DispatcherType#REQUEST} and {@link DispatcherType#ASYNC} and with 
124  * <code>&lt;async-supported&gt;true&lt;/async-supported&gt;</code>.
125  * </p>
126  */
127 @ManagedObject("limits exposure to abuse from request flooding, whether malicious, or as a result of a misconfigured client")
128 public class DoSFilter implements Filter
129 {
130     private static final Logger LOG = Log.getLogger(DoSFilter.class);
131 
132     private static final String IPv4_GROUP = "(\\d{1,3})";
133     private static final Pattern IPv4_PATTERN = Pattern.compile(IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP+"\\."+IPv4_GROUP);
134     private static final String IPv6_GROUP = "(\\p{XDigit}{1,4})";
135     private static final Pattern IPv6_PATTERN = Pattern.compile(IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP+":"+IPv6_GROUP);
136     private static final Pattern CIDR_PATTERN = Pattern.compile("([^/]+)/(\\d+)");
137 
138     private static final String __TRACKER = "DoSFilter.Tracker";
139     private static final String __THROTTLED = "DoSFilter.Throttled";
140 
141     private static final int __DEFAULT_MAX_REQUESTS_PER_SEC = 25;
142     private static final int __DEFAULT_DELAY_MS = 100;
143     private static final int __DEFAULT_THROTTLE = 5;
144     private static final int __DEFAULT_MAX_WAIT_MS = 50;
145     private static final long __DEFAULT_THROTTLE_MS = 30000L;
146     private static final long __DEFAULT_MAX_REQUEST_MS_INIT_PARAM = 30000L;
147     private static final long __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM = 30000L;
148 
149     static final String MANAGED_ATTR_INIT_PARAM = "managedAttr";
150     static final String MAX_REQUESTS_PER_S_INIT_PARAM = "maxRequestsPerSec";
151     static final String DELAY_MS_INIT_PARAM = "delayMs";
152     static final String THROTTLED_REQUESTS_INIT_PARAM = "throttledRequests";
153     static final String MAX_WAIT_INIT_PARAM = "maxWaitMs";
154     static final String THROTTLE_MS_INIT_PARAM = "throttleMs";
155     static final String MAX_REQUEST_MS_INIT_PARAM = "maxRequestMs";
156     static final String MAX_IDLE_TRACKER_MS_INIT_PARAM = "maxIdleTrackerMs";
157     static final String INSERT_HEADERS_INIT_PARAM = "insertHeaders";
158     static final String TRACK_SESSIONS_INIT_PARAM = "trackSessions";
159     static final String REMOTE_PORT_INIT_PARAM = "remotePort";
160     static final String IP_WHITELIST_INIT_PARAM = "ipWhitelist";
161     static final String ENABLED_INIT_PARAM = "enabled";
162     static final String TOO_MANY_CODE = "tooManyCode";
163 
164     private static final int USER_AUTH = 2;
165     private static final int USER_SESSION = 2;
166     private static final int USER_IP = 1;
167     private static final int USER_UNKNOWN = 0;
168 
169     private final String _suspended = "DoSFilter@" + Integer.toHexString(hashCode()) + ".SUSPENDED";
170     private final String _resumed = "DoSFilter@" + Integer.toHexString(hashCode()) + ".RESUMED";
171     private final ConcurrentHashMap<String, RateTracker> _rateTrackers = new ConcurrentHashMap<>();
172     private final List<String> _whitelist = new CopyOnWriteArrayList<>();
173     private int _tooManyCode;
174     private volatile long _delayMs;
175     private volatile long _throttleMs;
176     private volatile long _maxWaitMs;
177     private volatile long _maxRequestMs;
178     private volatile long _maxIdleTrackerMs;
179     private volatile boolean _insertHeaders;
180     private volatile boolean _trackSessions;
181     private volatile boolean _remotePort;
182     private volatile boolean _enabled;
183     private Semaphore _passes;
184     private volatile int _throttledRequests;
185     private volatile int _maxRequestsPerSec;
186     private Queue<AsyncContext>[] _queues;
187     private AsyncListener[] _listeners;
188     private Scheduler _scheduler;
189 
190     public void init(FilterConfig filterConfig) throws ServletException
191     {
192         _queues = new Queue[getMaxPriority() + 1];
193         _listeners = new AsyncListener[_queues.length];
194         for (int p = 0; p < _queues.length; p++)
195         {
196             _queues[p] = new ConcurrentLinkedQueue<>();
197             _listeners[p] = new DoSAsyncListener(p);
198         }
199 
200         _rateTrackers.clear();
201 
202         int maxRequests = __DEFAULT_MAX_REQUESTS_PER_SEC;
203         String parameter = filterConfig.getInitParameter(MAX_REQUESTS_PER_S_INIT_PARAM);
204         if (parameter != null)
205             maxRequests = Integer.parseInt(parameter);
206         setMaxRequestsPerSec(maxRequests);
207 
208         long delay = __DEFAULT_DELAY_MS;
209         parameter = filterConfig.getInitParameter(DELAY_MS_INIT_PARAM);
210         if (parameter != null)
211             delay = Long.parseLong(parameter);
212         setDelayMs(delay);
213 
214         int throttledRequests = __DEFAULT_THROTTLE;
215         parameter = filterConfig.getInitParameter(THROTTLED_REQUESTS_INIT_PARAM);
216         if (parameter != null)
217             throttledRequests = Integer.parseInt(parameter);
218         setThrottledRequests(throttledRequests);
219 
220         long maxWait = __DEFAULT_MAX_WAIT_MS;
221         parameter = filterConfig.getInitParameter(MAX_WAIT_INIT_PARAM);
222         if (parameter != null)
223             maxWait = Long.parseLong(parameter);
224         setMaxWaitMs(maxWait);
225 
226         long throttle = __DEFAULT_THROTTLE_MS;
227         parameter = filterConfig.getInitParameter(THROTTLE_MS_INIT_PARAM);
228         if (parameter != null)
229             throttle = Long.parseLong(parameter);
230         setThrottleMs(throttle);
231 
232         long maxRequestMs = __DEFAULT_MAX_REQUEST_MS_INIT_PARAM;
233         parameter = filterConfig.getInitParameter(MAX_REQUEST_MS_INIT_PARAM);
234         if (parameter != null)
235             maxRequestMs = Long.parseLong(parameter);
236         setMaxRequestMs(maxRequestMs);
237 
238         long maxIdleTrackerMs = __DEFAULT_MAX_IDLE_TRACKER_MS_INIT_PARAM;
239         parameter = filterConfig.getInitParameter(MAX_IDLE_TRACKER_MS_INIT_PARAM);
240         if (parameter != null)
241             maxIdleTrackerMs = Long.parseLong(parameter);
242         setMaxIdleTrackerMs(maxIdleTrackerMs);
243 
244         String whiteList = "";
245         parameter = filterConfig.getInitParameter(IP_WHITELIST_INIT_PARAM);
246         if (parameter != null)
247             whiteList = parameter;
248         setWhitelist(whiteList);
249 
250         parameter = filterConfig.getInitParameter(INSERT_HEADERS_INIT_PARAM);
251         setInsertHeaders(parameter == null || Boolean.parseBoolean(parameter));
252 
253         parameter = filterConfig.getInitParameter(TRACK_SESSIONS_INIT_PARAM);
254         setTrackSessions(parameter == null || Boolean.parseBoolean(parameter));
255 
256         parameter = filterConfig.getInitParameter(REMOTE_PORT_INIT_PARAM);
257         setRemotePort(parameter != null && Boolean.parseBoolean(parameter));
258 
259         parameter = filterConfig.getInitParameter(ENABLED_INIT_PARAM);
260         setEnabled(parameter == null || Boolean.parseBoolean(parameter));
261         
262         parameter = filterConfig.getInitParameter(TOO_MANY_CODE);
263         setTooManyCode(parameter==null?429:Integer.parseInt(parameter));
264         
265         _scheduler = startScheduler();
266 
267         ServletContext context = filterConfig.getServletContext();
268         if (context != null && Boolean.parseBoolean(filterConfig.getInitParameter(MANAGED_ATTR_INIT_PARAM)))
269             context.setAttribute(filterConfig.getFilterName(), this);
270     }
271 
272     protected Scheduler startScheduler() throws ServletException
273     {
274         try
275         {
276             Scheduler result = new ScheduledExecutorScheduler();
277             result.start();
278             return result;
279         }
280         catch (Exception x)
281         {
282             throw new ServletException(x);
283         }
284     }
285 
286     public void doFilter(ServletRequest request, ServletResponse response, FilterChain filterChain) throws IOException, ServletException
287     {
288         doFilter((HttpServletRequest)request, (HttpServletResponse)response, filterChain);
289     }
290 
291     protected void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException
292     {
293         if (!isEnabled())
294         {
295             filterChain.doFilter(request, response);
296             return;
297         }
298 
299         // Look for the rate tracker for this request.
300         RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
301         if (tracker == null)
302         {
303             // This is the first time we have seen this request.
304             if (LOG.isDebugEnabled())
305                 LOG.debug("Filtering {}", request);
306 
307             // Get a rate tracker associated with this request, and record one hit.
308             tracker = getRateTracker(request);
309 
310             // Calculate the rate and check it is over the allowed limit
311             final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis());
312 
313             // Pass it through if  we are not currently over the rate limit.
314             if (!overRateLimit)
315             {
316                 if (LOG.isDebugEnabled())
317                     LOG.debug("Allowing {}", request);
318                 doFilterChain(filterChain, request, response);
319                 return;
320             }
321 
322             // We are over the limit.
323 
324             // So either reject it, delay it or throttle it.
325             long delayMs = getDelayMs();
326             boolean insertHeaders = isInsertHeaders();
327             switch ((int)delayMs)
328             {
329                 case -1:
330                 {
331                     // Reject this request.
332                     LOG.warn("DOS ALERT: Request rejected ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
333                     if (insertHeaders)
334                         response.addHeader("DoSFilter", "unavailable");
335                     response.sendError(getTooManyCode());
336                     return;
337                 }
338                 case 0:
339                 {
340                     // Fall through to throttle the request.
341                     LOG.warn("DOS ALERT: Request throttled ip={}, session={}, user={}", request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
342                     request.setAttribute(__TRACKER, tracker);
343                     break;
344                 }
345                 default:
346                 {
347                     // Insert a delay before throttling the request,
348                     // using the suspend+timeout mechanism of AsyncContext.
349                     LOG.warn("DOS ALERT: Request delayed={}ms, ip={}, session={}, user={}", delayMs, request.getRemoteAddr(), request.getRequestedSessionId(), request.getUserPrincipal());
350                     if (insertHeaders)
351                         response.addHeader("DoSFilter", "delayed");
352                     request.setAttribute(__TRACKER, tracker);
353                     AsyncContext asyncContext = request.startAsync();
354                     if (delayMs > 0)
355                         asyncContext.setTimeout(delayMs);
356                     asyncContext.addListener(new DoSTimeoutAsyncListener());
357                     return;
358                 }
359             }
360         }
361 
362         if (LOG.isDebugEnabled())
363             LOG.debug("Throttling {}", request);
364 
365         // Throttle the request.
366         boolean accepted = false;
367         try
368         {
369             // Check if we can afford to accept another request at this time.
370             accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
371             if (!accepted)
372             {
373                 // We were not accepted, so either we suspend to wait,
374                 // or if we were woken up we insist or we fail.
375                 Boolean throttled = (Boolean)request.getAttribute(__THROTTLED);
376                 long throttleMs = getThrottleMs();
377                 if (throttled != Boolean.TRUE && throttleMs > 0)
378                 {
379                     int priority = getPriority(request, tracker);
380                     request.setAttribute(__THROTTLED, Boolean.TRUE);
381                     if (isInsertHeaders())
382                         response.addHeader("DoSFilter", "throttled");
383                     AsyncContext asyncContext = request.startAsync();
384                     request.setAttribute(_suspended, Boolean.TRUE);
385                     if (throttleMs > 0)
386                         asyncContext.setTimeout(throttleMs);
387                     asyncContext.addListener(_listeners[priority]);
388                     _queues[priority].add(asyncContext);
389                     if (LOG.isDebugEnabled())
390                         LOG.debug("Throttled {}, {}ms", request, throttleMs);
391                     return;
392                 }
393 
394                 Boolean resumed = (Boolean)request.getAttribute(_resumed);
395                 if (resumed == Boolean.TRUE)
396                 {
397                     // We were resumed, we wait for the next pass.
398                     _passes.acquire();
399                     accepted = true;
400                 }
401             }
402 
403             // If we were accepted (either immediately or after throttle)...
404             if (accepted)
405             {
406                 // ...call the chain.
407                 if (LOG.isDebugEnabled())
408                     LOG.debug("Allowing {}", request);
409                 doFilterChain(filterChain, request, response);
410             }
411             else
412             {
413                 // ...otherwise fail the request.
414                 if (LOG.isDebugEnabled())
415                     LOG.debug("Rejecting {}", request);
416                 if (isInsertHeaders())
417                     response.addHeader("DoSFilter", "unavailable");
418                 response.sendError(getTooManyCode());
419             }
420         }
421         catch (InterruptedException e)
422         {
423             LOG.ignore(e);
424             response.sendError(getTooManyCode());
425         }
426         finally
427         {
428             if (accepted)
429             {
430                 // Wake up the next highest priority request.
431                 for (int p = _queues.length - 1; p >= 0; --p)
432                 {
433                     AsyncContext asyncContext = _queues[p].poll();
434                     if (asyncContext != null)
435                     {
436                         ServletRequest candidate = asyncContext.getRequest();
437                         Boolean suspended = (Boolean)candidate.getAttribute(_suspended);
438                         if (suspended == Boolean.TRUE)
439                         {
440                             if (LOG.isDebugEnabled())
441                                 LOG.debug("Resuming {}", request);
442                             candidate.setAttribute(_resumed, Boolean.TRUE);
443                             asyncContext.dispatch();
444                             break;
445                         }
446                     }
447                 }
448                 _passes.release();
449             }
450         }
451     }
452 
453     protected void doFilterChain(FilterChain chain, final HttpServletRequest request, final HttpServletResponse response) throws IOException, ServletException
454     {
455         final Thread thread = Thread.currentThread();
456         Runnable requestTimeout = new Runnable()
457         {
458             @Override
459             public void run()
460             {
461                 closeConnection(request, response, thread);
462             }
463         };
464         Scheduler.Task task = _scheduler.schedule(requestTimeout, getMaxRequestMs(), TimeUnit.MILLISECONDS);
465         try
466         {
467             chain.doFilter(request, response);
468         }
469         finally
470         {
471             task.cancel();
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      *
479      * @param request  current request
480      * @param response current response, which must be stopped
481      * @param thread   the handling thread
482      */
483     protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
484     {
485         // take drastic measures to return this response and stop this thread.
486         if (!response.isCommitted())
487         {
488             response.setHeader("Connection", "close");
489         }
490         try
491         {
492             try
493             {
494                 response.getWriter().close();
495             }
496             catch (IllegalStateException e)
497             {
498                 response.getOutputStream().close();
499             }
500         }
501         catch (IOException e)
502         {
503             LOG.warn(e);
504         }
505 
506         // interrupt the handling thread
507         thread.interrupt();
508     }
509 
510     /**
511      * Get priority for this request, based on user type
512      *
513      * @param request the current request
514      * @param tracker the rate tracker for this request
515      * @return the priority for this request
516      */
517     protected int getPriority(HttpServletRequest request, RateTracker tracker)
518     {
519         if (extractUserId(request) != null)
520             return USER_AUTH;
521         if (tracker != null)
522             return tracker.getType();
523         return USER_UNKNOWN;
524     }
525 
526     /**
527      * @return the maximum priority that we can assign to a request
528      */
529     protected int getMaxPriority()
530     {
531         return USER_AUTH;
532     }
533 
534     /**
535      * Return a request rate tracker associated with this connection; keeps
536      * track of this connection's request rate. If this is not the first request
537      * from this connection, return the existing object with the stored stats.
538      * If it is the first request, then create a new request tracker.
539      * <p>
540      * Assumes that each connection has an identifying characteristic, and goes
541      * through them in order, taking the first that matches: user id (logged
542      * in), session id, client IP address. Unidentifiable connections are lumped
543      * into one.
544      * <p>
545      * When a session expires, its rate tracker is automatically deleted.
546      *
547      * @param request the current request
548      * @return the request rate tracker for the current connection
549      */
550     public RateTracker getRateTracker(ServletRequest request)
551     {
552         HttpSession session = ((HttpServletRequest)request).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 (isTrackSessions() && session != null && !session.isNew())
563             {
564                 loadId = session.getId();
565                 type = USER_SESSION;
566             }
567             else
568             {
569                 loadId = isRemotePort() ? (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             boolean allowed = checkWhitelist(request.getRemoteAddr());
579             int maxRequestsPerSec = getMaxRequestsPerSec();
580             tracker = allowed ? new FixedRateTracker(loadId, type, maxRequestsPerSec)
581                     : new RateTracker(loadId, type, maxRequestsPerSec);
582             RateTracker existing = _rateTrackers.putIfAbsent(loadId, tracker);
583             if (existing != null)
584                 tracker = existing;
585 
586             if (type == USER_IP)
587             {
588                 // USER_IP expiration from _rateTrackers is handled by the _scheduler
589                 _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
590             }
591             else if (session != null)
592             {
593                 // USER_SESSION expiration from _rateTrackers are handled by the HttpSessionBindingListener
594                 session.setAttribute(__TRACKER, tracker);
595             }
596         }
597 
598         return tracker;
599     }
600 
601     protected boolean checkWhitelist(String candidate)
602     {
603         for (String address : _whitelist)
604         {
605             if (address.contains("/"))
606             {
607                 if (subnetMatch(address, candidate))
608                     return true;
609             }
610             else
611             {
612                 if (address.equals(candidate))
613                     return true;
614             }
615         }
616         return false;
617     }
618 
619     @Deprecated
620     protected boolean checkWhitelist(List<String> whitelist, String candidate)
621     {
622         for (String address : whitelist)
623         {
624             if (address.contains("/"))
625             {
626                 if (subnetMatch(address, candidate))
627                     return true;
628             }
629             else
630             {
631                 if (address.equals(candidate))
632                     return true;
633             }
634         }
635         return false;
636     }
637 
638     protected boolean subnetMatch(String subnetAddress, String address)
639     {
640         Matcher cidrMatcher = CIDR_PATTERN.matcher(subnetAddress);
641         if (!cidrMatcher.matches())
642             return false;
643 
644         String subnet = cidrMatcher.group(1);
645         int prefix;
646         try
647         {
648             prefix = Integer.parseInt(cidrMatcher.group(2));
649         }
650         catch (NumberFormatException x)
651         {
652             LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
653             return false;
654         }
655 
656         byte[] subnetBytes = addressToBytes(subnet);
657         if (subnetBytes == null)
658         {
659             LOG.info("Ignoring malformed CIDR address {}", subnetAddress);
660             return false;
661         }
662         byte[] addressBytes = addressToBytes(address);
663         if (addressBytes == null)
664         {
665             LOG.info("Ignoring malformed remote address {}", address);
666             return false;
667         }
668 
669         // Comparing IPv4 with IPv6 ?
670         int length = subnetBytes.length;
671         if (length != addressBytes.length)
672             return false;
673 
674         byte[] mask = prefixToBytes(prefix, length);
675 
676         for (int i = 0; i < length; ++i)
677         {
678             if ((subnetBytes[i] & mask[i]) != (addressBytes[i] & mask[i]))
679                 return false;
680         }
681 
682         return true;
683     }
684 
685     private byte[] addressToBytes(String address)
686     {
687         Matcher ipv4Matcher = IPv4_PATTERN.matcher(address);
688         if (ipv4Matcher.matches())
689         {
690             byte[] result = new byte[4];
691             for (int i = 0; i < result.length; ++i)
692                 result[i] = Integer.valueOf(ipv4Matcher.group(i + 1)).byteValue();
693             return result;
694         }
695         else
696         {
697             Matcher ipv6Matcher = IPv6_PATTERN.matcher(address);
698             if (ipv6Matcher.matches())
699             {
700                 byte[] result = new byte[16];
701                 for (int i = 0; i < result.length; i += 2)
702                 {
703                     int word = Integer.valueOf(ipv6Matcher.group(i / 2 + 1), 16);
704                     result[i] = (byte)((word & 0xFF00) >>> 8);
705                     result[i + 1] = (byte)(word & 0xFF);
706                 }
707                 return result;
708             }
709         }
710         return null;
711     }
712 
713     private byte[] prefixToBytes(int prefix, int length)
714     {
715         byte[] result = new byte[length];
716         int index = 0;
717         while (prefix / 8 > 0)
718         {
719             result[index] = -1;
720             prefix -= 8;
721             ++index;
722         }
723         
724         if (index == result.length)
725             return result;
726                
727         // Sets the _prefix_ most significant bits to 1
728         result[index] = (byte)~((1 << (8 - prefix)) - 1);
729         return result;
730     }
731 
732     public void destroy()
733     {
734         LOG.debug("Destroy {}",this);
735         stopScheduler();
736         _rateTrackers.clear();
737         _whitelist.clear();
738     }
739 
740     protected void stopScheduler()
741     {
742         try
743         {
744             _scheduler.stop();
745         }
746         catch (Exception x)
747         {
748             LOG.ignore(x);
749         }
750     }
751 
752     /**
753      * Returns the user id, used to track this connection.
754      * This SHOULD be overridden by subclasses.
755      *
756      * @param request the current request
757      * @return a unique user id, if logged in; otherwise null.
758      */
759     protected String extractUserId(ServletRequest request)
760     {
761         return null;
762     }
763 
764     /**
765      * Get maximum number of requests from a connection per
766      * second. Requests in excess of this are first delayed,
767      * then throttled.
768      *
769      * @return maximum number of requests
770      */
771     @ManagedAttribute("maximum number of requests allowed from a connection per second")
772     public int getMaxRequestsPerSec()
773     {
774         return _maxRequestsPerSec;
775     }
776 
777     /**
778      * Get maximum number of requests from a connection per
779      * second. Requests in excess of this are first delayed,
780      * then throttled.
781      *
782      * @param value maximum number of requests
783      */
784     public void setMaxRequestsPerSec(int value)
785     {
786         _maxRequestsPerSec = value;
787     }
788 
789     /**
790      * Get delay (in milliseconds) that is applied to all requests
791      * over the rate limit, before they are considered at all.
792      * @return the delay in milliseconds
793      */
794     @ManagedAttribute("delay applied to all requests over the rate limit (in ms)")
795     public long getDelayMs()
796     {
797         return _delayMs;
798     }
799 
800     /**
801      * Set delay (in milliseconds) that is applied to all requests
802      * over the rate limit, before they are considered at all.
803      *
804      * @param value delay (in milliseconds), 0 - no delay, -1 - reject request
805      */
806     public void setDelayMs(long value)
807     {
808         _delayMs = value;
809     }
810 
811     /**
812      * Get maximum amount of time (in milliseconds) the filter will
813      * blocking wait for the throttle semaphore.
814      *
815      * @return maximum wait time
816      */
817     @ManagedAttribute("maximum time the filter will block waiting throttled connections, (0 for no delay, -1 to reject requests)")
818     public long getMaxWaitMs()
819     {
820         return _maxWaitMs;
821     }
822 
823     /**
824      * Set maximum amount of time (in milliseconds) the filter will
825      * blocking wait for the throttle semaphore.
826      *
827      * @param value maximum wait time
828      */
829     public void setMaxWaitMs(long value)
830     {
831         _maxWaitMs = value;
832     }
833 
834     /**
835      * Get number of requests over the rate limit able to be
836      * considered at once.
837      *
838      * @return number of requests
839      */
840     @ManagedAttribute("number of requests over rate limit")
841     public int getThrottledRequests()
842     {
843         return _throttledRequests;
844     }
845 
846     /**
847      * Set number of requests over the rate limit able to be
848      * considered at once.
849      *
850      * @param value number of requests
851      */
852     public void setThrottledRequests(int value)
853     {
854         int permits = _passes == null ? 0 : _passes.availablePermits();
855         _passes = new Semaphore((value - _throttledRequests + permits), true);
856         _throttledRequests = value;
857     }
858 
859     /**
860      * Get amount of time (in milliseconds) to async wait for semaphore.
861      *
862      * @return wait time
863      */
864     @ManagedAttribute("amount of time to async wait for semaphore")
865     public long getThrottleMs()
866     {
867         return _throttleMs;
868     }
869 
870     /**
871      * Set amount of time (in milliseconds) to async wait for semaphore.
872      *
873      * @param value wait time
874      */
875     public void setThrottleMs(long value)
876     {
877         _throttleMs = value;
878     }
879 
880     /**
881      * Get maximum amount of time (in milliseconds) to allow
882      * the request to process.
883      *
884      * @return maximum processing time
885      */
886     @ManagedAttribute("maximum time to allow requests to process (in ms)")
887     public long getMaxRequestMs()
888     {
889         return _maxRequestMs;
890     }
891 
892     /**
893      * Set maximum amount of time (in milliseconds) to allow
894      * the request to process.
895      *
896      * @param value maximum processing time
897      */
898     public void setMaxRequestMs(long value)
899     {
900         _maxRequestMs = value;
901     }
902 
903     /**
904      * Get maximum amount of time (in milliseconds) to keep track
905      * of request rates for a connection, before deciding that
906      * the user has gone away, and discarding it.
907      *
908      * @return maximum tracking time
909      */
910     @ManagedAttribute("maximum time to track of request rates for connection before discarding")
911     public long getMaxIdleTrackerMs()
912     {
913         return _maxIdleTrackerMs;
914     }
915 
916     /**
917      * Set maximum amount of time (in milliseconds) to keep track
918      * of request rates for a connection, before deciding that
919      * the user has gone away, and discarding it.
920      *
921      * @param value maximum tracking time
922      */
923     public void setMaxIdleTrackerMs(long value)
924     {
925         _maxIdleTrackerMs = value;
926     }
927 
928     /**
929      * Check flag to insert the DoSFilter headers into the response.
930      *
931      * @return value of the flag
932      */
933     @ManagedAttribute("inser DoSFilter headers in response")
934     public boolean isInsertHeaders()
935     {
936         return _insertHeaders;
937     }
938 
939     /**
940      * Set flag to insert the DoSFilter headers into the response.
941      *
942      * @param value value of the flag
943      */
944     public void setInsertHeaders(boolean value)
945     {
946         _insertHeaders = value;
947     }
948 
949     /**
950      * Get flag to have usage rate tracked by session if a session exists.
951      *
952      * @return value of the flag
953      */
954     @ManagedAttribute("usage rate is tracked by session if one exists")
955     public boolean isTrackSessions()
956     {
957         return _trackSessions;
958     }
959 
960     /**
961      * Set flag to have usage rate tracked by session if a session exists.
962      *
963      * @param value value of the flag
964      */
965     public void setTrackSessions(boolean value)
966     {
967         _trackSessions = value;
968     }
969 
970     /**
971      * Get flag to have usage rate tracked by IP+port (effectively connection)
972      * if session tracking is not used.
973      *
974      * @return value of the flag
975      */
976     @ManagedAttribute("usage rate is tracked by IP+port is session tracking not used")
977     public boolean isRemotePort()
978     {
979         return _remotePort;
980     }
981 
982     /**
983      * Set flag to have usage rate tracked by IP+port (effectively connection)
984      * if session tracking is not used.
985      *
986      * @param value value of the flag
987      */
988     public void setRemotePort(boolean value)
989     {
990         _remotePort = value;
991     }
992 
993     /**
994      * @return whether this filter is enabled
995      */
996     @ManagedAttribute("whether this filter is enabled")
997     public boolean isEnabled()
998     {
999         return _enabled;
1000     }
1001 
1002     /**
1003      * @param enabled whether this filter is enabled
1004      */
1005     public void setEnabled(boolean enabled)
1006     {
1007         _enabled = enabled;
1008     }
1009     
1010     public int getTooManyCode()
1011     {
1012         return _tooManyCode;
1013     }
1014 
1015     public void setTooManyCode(int tooManyCode)
1016     {
1017         _tooManyCode = tooManyCode;
1018     }
1019 
1020     /**
1021      * Get a list of IP addresses that will not be rate limited.
1022      *
1023      * @return comma-separated whitelist
1024      */
1025     @ManagedAttribute("list of IPs that will not be rate limited")
1026     public String getWhitelist()
1027     {
1028         StringBuilder result = new StringBuilder();
1029         for (Iterator<String> iterator = _whitelist.iterator(); iterator.hasNext();)
1030         {
1031             String address = iterator.next();
1032             result.append(address);
1033             if (iterator.hasNext())
1034                 result.append(",");
1035         }
1036         return result.toString();
1037     }
1038 
1039     /**
1040      * Set a list of IP addresses that will not be rate limited.
1041      *
1042      * @param commaSeparatedList comma-separated whitelist
1043      */
1044     public void setWhitelist(String commaSeparatedList)
1045     {
1046         List<String> result = new ArrayList<>();
1047         for (String address : StringUtil.csvSplit(commaSeparatedList))
1048             addWhitelistAddress(result, address);
1049         clearWhitelist();
1050         _whitelist.addAll(result);
1051         LOG.debug("Whitelisted IP addresses: {}", result);
1052     }
1053 
1054     /**
1055      * Clears the list of whitelisted IP addresses
1056      */
1057     @ManagedOperation("clears the list of IP addresses that will not be rate limited")
1058     public void clearWhitelist()
1059     {
1060         _whitelist.clear();
1061     }
1062 
1063     /**
1064      * Adds the given IP address, either in the form of a dotted decimal notation A.B.C.D
1065      * or in the CIDR notation A.B.C.D/M, to the list of whitelisted IP addresses.
1066      *
1067      * @param address the address to add
1068      * @return whether the address was added to the list
1069      * @see #removeWhitelistAddress(String)
1070      */
1071     @ManagedOperation("adds an IP address that will not be rate limited")
1072     public boolean addWhitelistAddress(@Name("address") String address)
1073     {
1074         return addWhitelistAddress(_whitelist, address);
1075     }
1076 
1077     private boolean addWhitelistAddress(List<String> list, String address)
1078     {
1079         address = address.trim();
1080         return address.length() > 0 && list.add(address);
1081     }
1082 
1083     /**
1084      * Removes the given address from the list of whitelisted IP addresses.
1085      *
1086      * @param address the address to remove
1087      * @return whether the address was removed from the list
1088      * @see #addWhitelistAddress(String)
1089      */
1090     @ManagedOperation("removes an IP address that will not be rate limited")
1091     public boolean removeWhitelistAddress(@Name("address") String address)
1092     {
1093         return _whitelist.remove(address);
1094     }
1095 
1096     /**
1097      * A RateTracker is associated with a connection, and stores request rate
1098      * data.
1099      */
1100     class RateTracker implements Runnable, HttpSessionBindingListener, HttpSessionActivationListener, Serializable
1101     {
1102         private static final long serialVersionUID = 3534663738034577872L;
1103 
1104         protected transient final String _id;
1105         protected transient final int _type;
1106         protected transient final long[] _timestamps;
1107         protected transient int _next;
1108 
1109         public RateTracker(String id, int type, int maxRequestsPerSecond)
1110         {
1111             _id = id;
1112             _type = type;
1113             _timestamps = new long[maxRequestsPerSecond];
1114             _next = 0;
1115         }
1116 
1117         /**
1118          * @param now the time now (in milliseconds)
1119          * @return the current calculated request rate over the last second
1120          */
1121         public boolean isRateExceeded(long now)
1122         {
1123             final long last;
1124             synchronized (this)
1125             {
1126                 last = _timestamps[_next];
1127                 _timestamps[_next] = now;
1128                 _next = (_next + 1) % _timestamps.length;
1129             }
1130 
1131             return last != 0 && (now - last) < 1000L;
1132         }
1133 
1134         public String getId()
1135         {
1136             return _id;
1137         }
1138 
1139         public int getType()
1140         {
1141             return _type;
1142         }
1143 
1144         public void valueBound(HttpSessionBindingEvent event)
1145         {
1146             if (LOG.isDebugEnabled())
1147                 LOG.debug("Value bound: {}", getId());
1148         }
1149 
1150         public void valueUnbound(HttpSessionBindingEvent event)
1151         {
1152             //take the tracker out of the list of trackers
1153             _rateTrackers.remove(_id);
1154             if (LOG.isDebugEnabled())
1155                 LOG.debug("Tracker removed: {}", getId());
1156         }
1157 
1158         public void sessionWillPassivate(HttpSessionEvent se)
1159         {
1160             //take the tracker of the list of trackers (if its still there)
1161             //and ensure that we take ourselves out of the session so we are not saved
1162             _rateTrackers.remove(_id);
1163             se.getSession().removeAttribute(__TRACKER);
1164             if (LOG.isDebugEnabled()) 
1165                 LOG.debug("Value removed: {}", getId());
1166         }
1167 
1168         public void sessionDidActivate(HttpSessionEvent se)
1169         {
1170             LOG.warn("Unexpected session activation");
1171         }
1172 
1173         @Override
1174         public void run()
1175         {
1176             int latestIndex = _next == 0 ? (_timestamps.length - 1) : (_next - 1);
1177             long last = _timestamps[latestIndex];
1178             boolean hasRecentRequest = last != 0 && (System.currentTimeMillis() - last) < 1000L;
1179 
1180             if (hasRecentRequest)
1181                 _scheduler.schedule(this, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
1182             else
1183                 _rateTrackers.remove(_id);
1184         }
1185 
1186         @Override
1187         public String toString()
1188         {
1189             return "RateTracker/" + _id + "/" + _type;
1190         }
1191     }
1192 
1193     class FixedRateTracker extends RateTracker
1194     {
1195         public FixedRateTracker(String id, int type, int numRecentRequestsTracked)
1196         {
1197             super(id, type, numRecentRequestsTracked);
1198         }
1199 
1200         @Override
1201         public boolean isRateExceeded(long now)
1202         {
1203             // rate limit is never exceeded, but we keep track of the request timestamps
1204             // so that we know whether there was recent activity on this tracker
1205             // and whether it should be expired
1206             synchronized (this)
1207             {
1208                 _timestamps[_next] = now;
1209                 _next = (_next + 1) % _timestamps.length;
1210             }
1211 
1212             return false;
1213         }
1214 
1215         @Override
1216         public String toString()
1217         {
1218             return "Fixed" + super.toString();
1219         }
1220     }
1221 
1222     private class DoSTimeoutAsyncListener implements AsyncListener
1223     {
1224         @Override
1225         public void onStartAsync(AsyncEvent event) throws IOException
1226         {
1227         }
1228 
1229         @Override
1230         public void onComplete(AsyncEvent event) throws IOException
1231         {
1232         }
1233 
1234         @Override
1235         public void onTimeout(AsyncEvent event) throws IOException
1236         {
1237             event.getAsyncContext().dispatch();
1238         }
1239 
1240         @Override
1241         public void onError(AsyncEvent event) throws IOException
1242         {
1243         }
1244     }
1245 
1246     private class DoSAsyncListener extends DoSTimeoutAsyncListener
1247     {
1248         private final int priority;
1249 
1250         public DoSAsyncListener(int priority)
1251         {
1252             this.priority = priority;
1253         }
1254 
1255         @Override
1256         public void onTimeout(AsyncEvent event) throws IOException
1257         {
1258             _queues[priority].remove(event.getAsyncContext());
1259             super.onTimeout(event);
1260         }
1261     }
1262 }