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