xref: /aosp_15_r20/external/zxing/zxingorg/src/main/java/com/google/zxing/web/DoSTracker.java (revision 513427e33d61bc67fc40bc261642ac0b2a686b45)
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