1 /* 2 * Copyright 2017 ZXing authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.google.zxing.web; 18 19 import java.lang.management.ManagementFactory; 20 import java.lang.management.OperatingSystemMXBean; 21 import java.util.Iterator; 22 import java.util.Map; 23 import java.util.Timer; 24 import java.util.TimerTask; 25 import java.util.concurrent.atomic.AtomicInteger; 26 import java.util.logging.Logger; 27 28 /** 29 * Simple class which tracks a number of actions that happen per time and can flag when an action has 30 * happened too frequently recently. This can be used for example to track and temporarily block access 31 * from certain IPs or to certain hosts. 32 */ 33 final class DoSTracker { 34 35 private static final Logger log = Logger.getLogger(DoSTracker.class.getName()); 36 37 private volatile int maxAccessesPerTime; 38 private final Map<String,AtomicInteger> numRecentAccesses; 39 40 /** 41 * @param timer {@link Timer} to use for scheduling update tasks 42 * @param name identifier for this tracker 43 * @param maxAccessesPerTime maximum number of accesses allowed from one source per {@code accessTimeMS} 44 * @param accessTimeMS interval in milliseconds over which up to {@code maxAccessesPerTime} accesses are allowed 45 * @param maxEntries maximum number of source entries to track before forgetting least recent ones 46 * @param maxLoad if set, dynamically adjust {@code maxAccessesPerTime} downwards when average load per core 47 * exceeds this value, and upwards when below this value 48 */ DoSTracker(Timer timer, String name, int maxAccessesPerTime, long accessTimeMS, int maxEntries, Double maxLoad)49 DoSTracker(Timer timer, 50 String name, 51 int maxAccessesPerTime, 52 long accessTimeMS, 53 int maxEntries, 54 Double maxLoad) { 55 this.maxAccessesPerTime = maxAccessesPerTime; 56 this.numRecentAccesses = new LRUMap<>(maxEntries); 57 timer.schedule(new TrackerTask(name, maxLoad), accessTimeMS, accessTimeMS); 58 } 59 isBanned(String event)60 boolean isBanned(String event) { 61 if (event == null) { 62 return true; 63 } 64 AtomicInteger count; 65 synchronized (numRecentAccesses) { 66 count = numRecentAccesses.get(event); 67 if (count == null) { 68 numRecentAccesses.put(event, new AtomicInteger(1)); 69 return false; 70 } 71 } 72 return count.incrementAndGet() > maxAccessesPerTime; 73 } 74 75 private final class TrackerTask extends TimerTask { 76 77 private final String name; 78 private final Double maxLoad; 79 TrackerTask(String name, Double maxLoad)80 private TrackerTask(String name, Double maxLoad) { 81 this.name = name; 82 this.maxLoad = maxLoad; 83 } 84 85 @Override run()86 public void run() { 87 // largest count <= maxAccessesPerTime 88 int maxAllowedCount = 1; 89 // smallest count > maxAccessesPerTime 90 int minDisallowedCount = Integer.MAX_VALUE; 91 int localMAPT = maxAccessesPerTime; 92 int totalEntries; 93 int clearedEntries = 0; 94 synchronized (numRecentAccesses) { 95 totalEntries = numRecentAccesses.size(); 96 Iterator<Map.Entry<String,AtomicInteger>> accessIt = numRecentAccesses.entrySet().iterator(); 97 while (accessIt.hasNext()) { 98 Map.Entry<String,AtomicInteger> entry = accessIt.next(); 99 AtomicInteger atomicCount = entry.getValue(); 100 int count = atomicCount.get(); 101 // If number of accesses is below the threshold, remove it entirely 102 if (count <= localMAPT) { 103 accessIt.remove(); 104 maxAllowedCount = Math.max(maxAllowedCount, count); 105 clearedEntries++; 106 } else { 107 // Reduce count of accesses held against the host 108 atomicCount.getAndAdd(-localMAPT); 109 minDisallowedCount = Math.min(minDisallowedCount, count); 110 } 111 } 112 } 113 log.info(name + ": " + clearedEntries + " of " + totalEntries + " cleared"); 114 115 if (maxLoad != null) { 116 OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean(); 117 if (mxBean == null) { 118 log.warning("Could not obtain OperatingSystemMXBean; ignoring load"); 119 } else { 120 double loadAvg = mxBean.getSystemLoadAverage(); 121 if (loadAvg >= 0.0) { 122 int cores = mxBean.getAvailableProcessors(); 123 double loadRatio = loadAvg / cores; 124 int newMaxAccessesPerTime = loadRatio > maxLoad ? 125 Math.min(maxAllowedCount, Math.max(1, maxAccessesPerTime - 1)) : 126 Math.max(minDisallowedCount, maxAccessesPerTime); 127 log.info(name + ": Load ratio: " + loadRatio + 128 " (" + loadAvg + '/' + cores + ") vs " + maxLoad + 129 " ; new maxAccessesPerTime: " + newMaxAccessesPerTime); 130 maxAccessesPerTime = newMaxAccessesPerTime; 131 } 132 } 133 } 134 } 135 136 } 137 138 } 139