View Javadoc

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