-
Notifications
You must be signed in to change notification settings - Fork 2
/
cache.go
158 lines (129 loc) · 3.56 KB
/
cache.go
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
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
package traefik_plugin_fail2ban //nolint:revive,stylecheck
import (
"sync"
"sync/atomic"
"time"
)
const minWaitTimeBetweenCacheCleans = 10 * time.Minute
// Cache implementation for caching of IP entries.
type Cache struct {
lock sync.RWMutex
entries map[string]*CacheEntry
lastCleaned atomic.Int64
}
// CacheEntry represents single entry/IP in cache.
type CacheEntry struct {
firstSeen atomic.Int64
lastSeen atomic.Int64
timesSeen atomic.Uint32
isBanned atomic.Bool
}
// SetFirstSeen sets firstSeen.
func (ce *CacheEntry) SetFirstSeen(t time.Time) {
ce.firstSeen.Store(t.UnixMilli())
}
// GetFirstSeen gets firstSeen.
func (ce *CacheEntry) GetFirstSeen() time.Time {
return time.UnixMilli(ce.firstSeen.Load())
}
// SetLastSeen sets lastSeen.
func (ce *CacheEntry) SetLastSeen(t time.Time) {
ce.lastSeen.Store(t.UnixMilli())
}
// GetLastSeen gets lastSeen.
func (ce *CacheEntry) GetLastSeen() time.Time {
return time.UnixMilli(ce.lastSeen.Load())
}
// IncrementTimesSeen increments timesSeen.
func (ce *CacheEntry) IncrementTimesSeen() {
ce.timesSeen.Store(ce.timesSeen.Load() + 1)
}
// GetTimesSeen gets timesSeen.
func (ce *CacheEntry) GetTimesSeen() uint32 {
return ce.timesSeen.Load()
}
// IssueBan issues a ban for the entry/ip.
func (ce *CacheEntry) IssueBan() {
ce.isBanned.Store(true)
}
// IsBanned checks if entry is banned.
func (ce *CacheEntry) IsBanned() bool {
return ce.isBanned.Load()
}
// NewCache creates new instance of caching.
func NewCache() *Cache {
return &Cache{
entries: make(map[string]*CacheEntry, 10000),
}
}
// GetEntry retrieve entry from cache.
func (c *Cache) GetEntry(key string) *CacheEntry {
c.lock.RLock()
defer c.lock.RUnlock()
return c.entries[key]
}
// CreateEntry retrieve entry from cache or create new entry with firstSeen.
func (c *Cache) CreateEntry(key string, firstSeen time.Time) *CacheEntry {
c.lock.Lock()
defer c.lock.Unlock()
entry, ok := c.entries[key]
if ok {
return entry
}
entry = &CacheEntry{}
entry.SetFirstSeen(firstSeen)
c.entries[key] = entry
return entry
}
// CleanEntryIfPossible removes entry from cache by key and limits.
func (c *Cache) CleanEntryIfPossible(key string, findTime, banTime time.Duration, now time.Time) {
findTimeBoundary := now.Add(-findTime)
banTimeBoundary := now.Add(-banTime)
entry := c.GetEntry(key)
if entry == nil {
return
}
if entry.IsBanned() && entry.GetLastSeen().Before(banTimeBoundary) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.entries, key)
return
}
if !entry.IsBanned() && entry.GetFirstSeen().Before(findTimeBoundary) {
c.lock.Lock()
defer c.lock.Unlock()
delete(c.entries, key)
return
}
}
// CleanEntries garbage collect entries.
func (c *Cache) CleanEntries(findTime, banTime time.Duration) (removedEntries int) {
lastCleanedRaw := c.lastCleaned.Load()
lastCleaned := time.UnixMilli(lastCleanedRaw)
now := time.Now()
if !lastCleaned.Add(minWaitTimeBetweenCacheCleans).Before(now) {
// cache was cleaned recently, nothing to do
return 0
}
if !c.lastCleaned.CompareAndSwap(lastCleanedRaw, now.UnixMilli()) {
// cache is already cleaned by other execution, nothing to do
return 0
}
c.lock.Lock()
defer c.lock.Unlock()
findTimeBoundary := now.Add(-findTime)
banTimeBoundary := now.Add(-banTime)
for key, entry := range c.entries {
if !entry.IsBanned() && entry.GetFirstSeen().Before(findTimeBoundary) {
delete(c.entries, key)
removedEntries++
}
}
for key, entry := range c.entries {
if entry.GetLastSeen().Before(banTimeBoundary) {
delete(c.entries, key)
removedEntries++
}
}
return removedEntries
}