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