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