summaryrefslogtreecommitdiff
path: root/zxingorg/src/main/java/com/google/zxing/web/DoSTracker.java
blob: 89032cdfa997d36beb02c27e181b869a983c32cd (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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
132
133
134
135
136
137
138
/*
 * Copyright 2017 ZXing authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.zxing.web;

import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import java.util.Iterator;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Logger;

/**
 * Simple class which tracks a number of actions that happen per time and can flag when an action has
 * happened too frequently recently. This can be used for example to track and temporarily block access
 * from certain IPs or to certain hosts.
 */
final class DoSTracker {

  private static final Logger log = Logger.getLogger(DoSTracker.class.getName());

  private volatile int maxAccessesPerTime;
  private final Map<String,AtomicInteger> numRecentAccesses;

  /**
   * @param timer {@link Timer} to use for scheduling update tasks
   * @param name identifier for this tracker
   * @param maxAccessesPerTime maximum number of accesses allowed from one source per {@code accessTimeMS}
   * @param accessTimeMS interval in milliseconds over which up to {@code maxAccessesPerTime} accesses are allowed
   * @param maxEntries maximum number of source entries to track before forgetting least recent ones
   * @param maxLoad if set, dynamically adjust {@code maxAccessesPerTime} downwards when average load per core
   *                exceeds this value, and upwards when below this value
   */
  DoSTracker(Timer timer,
             String name,
             int maxAccessesPerTime,
             long accessTimeMS,
             int maxEntries,
             Double maxLoad) {
    this.maxAccessesPerTime = maxAccessesPerTime;
    this.numRecentAccesses = new LRUMap<>(maxEntries);
    timer.schedule(new TrackerTask(name, maxLoad), accessTimeMS, accessTimeMS);
  }

  boolean isBanned(String event) {
    if (event == null) {
      return true;
    }
    AtomicInteger count;
    synchronized (numRecentAccesses) {
      count = numRecentAccesses.get(event);
      if (count == null) {
        numRecentAccesses.put(event, new AtomicInteger(1));
        return false;
      }
    }
    return count.incrementAndGet() > maxAccessesPerTime;
  }

  private final class TrackerTask extends TimerTask {

    private final String name;
    private final Double maxLoad;

    private TrackerTask(String name, Double maxLoad) {
      this.name = name;
      this.maxLoad = maxLoad;
    }

    @Override
    public void run() {
      // largest count <= maxAccessesPerTime
      int maxAllowedCount = 1;
      // smallest count > maxAccessesPerTime
      int minDisallowedCount = Integer.MAX_VALUE;
      int localMAPT = maxAccessesPerTime;
      int totalEntries;
      int clearedEntries = 0;
      synchronized (numRecentAccesses) {
        totalEntries = numRecentAccesses.size();
        Iterator<Map.Entry<String,AtomicInteger>> accessIt = numRecentAccesses.entrySet().iterator();
        while (accessIt.hasNext()) {
          Map.Entry<String,AtomicInteger> entry = accessIt.next();
          AtomicInteger atomicCount = entry.getValue();
          int count = atomicCount.get();
          // If number of accesses is below the threshold, remove it entirely
          if (count <= localMAPT) {
            accessIt.remove();
            maxAllowedCount = Math.max(maxAllowedCount, count);
            clearedEntries++;
          } else {
            // Reduce count of accesses held against the host
            atomicCount.getAndAdd(-localMAPT);
            minDisallowedCount = Math.min(minDisallowedCount, count);
          }
        }
      }
      log.info(name + ": " + clearedEntries + " of " + totalEntries + " cleared");

      if (maxLoad != null) {
        OperatingSystemMXBean mxBean = ManagementFactory.getOperatingSystemMXBean();
        if (mxBean == null) {
          log.warning("Could not obtain OperatingSystemMXBean; ignoring load");
        } else {
          double loadAvg = mxBean.getSystemLoadAverage();
          if (loadAvg >= 0.0) {
            int cores = mxBean.getAvailableProcessors();
            double loadRatio = loadAvg / cores;
            int newMaxAccessesPerTime = loadRatio > maxLoad ?
                Math.min(maxAllowedCount, Math.max(1, maxAccessesPerTime - 1)) :
                Math.max(minDisallowedCount, maxAccessesPerTime);
            log.info(name + ": Load ratio: " + loadRatio +
                " (" + loadAvg + '/' + cores + ") vs " + maxLoad +
                " ; new maxAccessesPerTime: " + newMaxAccessesPerTime);
            maxAccessesPerTime = newMaxAccessesPerTime;
          }
        }
      }
    }

  }

}