1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
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
311 RateTracker tracker = (RateTracker)request.getAttribute(__TRACKER);
312
313 if (tracker == null)
314 {
315
316
317
318 tracker = getRateTracker(request);
319
320
321 final boolean overRateLimit = tracker.isRateExceeded(System.currentTimeMillis());
322
323
324 if (!overRateLimit)
325 {
326 doFilterChain(filterChain, request, response);
327 return;
328 }
329
330
331
332
333 long delayMs = getDelayMs();
334 boolean insertHeaders = isInsertHeaders();
335 switch ((int)delayMs)
336 {
337 case -1:
338 {
339
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
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
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
370 boolean accepted = false;
371 try
372 {
373
374 accepted = _passes.tryAcquire(getMaxWaitMs(), TimeUnit.MILLISECONDS);
375
376 if (!accepted)
377 {
378
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
398 else if (request.getAttribute("javax.servlet.resumed") == Boolean.TRUE)
399 {
400
401 _passes.acquire();
402 accepted = true;
403 }
404 }
405
406
407 if (accepted)
408
409 doFilterChain(filterChain, request, response);
410 else
411 {
412
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
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
467
468
469
470
471
472
473 protected void closeConnection(HttpServletRequest request, HttpServletResponse response, Thread thread)
474 {
475
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
497 thread.interrupt();
498 }
499
500
501
502
503
504
505
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
518
519 protected int getMaxPriority()
520 {
521 return USER_AUTH;
522 }
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
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
579 _scheduler.schedule(tracker, getMaxIdleTrackerMs(), TimeUnit.MILLISECONDS);
580 }
581 else if (session != null)
582 {
583
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
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
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
721
722
723
724
725
726 protected String extractUserId(ServletRequest request)
727 {
728 return null;
729 }
730
731
732
733
734
735
736
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
746
747
748
749
750
751 public void setMaxRequestsPerSec(int value)
752 {
753 _maxRequestsPerSec = value;
754 }
755
756
757
758
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
768
769
770
771
772 public void setDelayMs(long value)
773 {
774 _delayMs = value;
775 }
776
777
778
779
780
781
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
791
792
793
794
795 public void setMaxWaitMs(long value)
796 {
797 _maxWaitMs = value;
798 }
799
800
801
802
803
804
805
806 @ManagedAttribute("number of requests over rate limit")
807 public int getThrottledRequests()
808 {
809 return _throttledRequests;
810 }
811
812
813
814
815
816
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
827
828
829
830 @ManagedAttribute("amount of time to async wait for semaphore")
831 public long getThrottleMs()
832 {
833 return _throttleMs;
834 }
835
836
837
838
839
840
841 public void setThrottleMs(long value)
842 {
843 _throttleMs = value;
844 }
845
846
847
848
849
850
851
852 @ManagedAttribute("maximum time to allow requests to process (in ms)")
853 public long getMaxRequestMs()
854 {
855 return _maxRequestMs;
856 }
857
858
859
860
861
862
863
864 public void setMaxRequestMs(long value)
865 {
866 _maxRequestMs = value;
867 }
868
869
870
871
872
873
874
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
884
885
886
887
888
889 public void setMaxIdleTrackerMs(long value)
890 {
891 _maxIdleTrackerMs = value;
892 }
893
894
895
896
897
898
899 @ManagedAttribute("inser DoSFilter headers in response")
900 public boolean isInsertHeaders()
901 {
902 return _insertHeaders;
903 }
904
905
906
907
908
909
910 public void setInsertHeaders(boolean value)
911 {
912 _insertHeaders = value;
913 }
914
915
916
917
918
919
920 @ManagedAttribute("usage rate is tracked by session if one exists")
921 public boolean isTrackSessions()
922 {
923 return _trackSessions;
924 }
925
926
927
928
929
930
931 public void setTrackSessions(boolean value)
932 {
933 _trackSessions = value;
934 }
935
936
937
938
939
940
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
950
951
952
953
954 public void setRemotePort(boolean value)
955 {
956 _remotePort = value;
957 }
958
959
960
961
962 @ManagedAttribute("whether this filter is enabled")
963 public boolean isEnabled()
964 {
965 return _enabled;
966 }
967
968
969
970
971 public void setEnabled(boolean enabled)
972 {
973 _enabled = enabled;
974 }
975
976
977
978
979
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
997
998
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
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
1021
1022
1023
1024
1025
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
1041
1042
1043
1044
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
1054
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
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
1108 _rateTrackers.remove(_id);
1109 if (LOG.isDebugEnabled())
1110 LOG.debug("Tracker removed: {}", getId());
1111 }
1112
1113 public void sessionWillPassivate(HttpSessionEvent se)
1114 {
1115
1116
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
1158
1159
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 }