View Javadoc

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