-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathlimiter.go
118 lines (101 loc) · 2.72 KB
/
limiter.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
package p2p
import (
"fmt"
"net"
"time"
)
// LimitedListener will block multiple connection attempts from a single ip
// within a specific timeframe
type LimitedListener struct {
listener net.Listener
limit time.Duration
lastConnection time.Time
history []attempt
}
type attempt struct {
host string
time time.Time
}
// NewLimitedListener initializes a new listener for the specified address (host:port)
// throttling incoming connections
func NewLimitedListener(address string, limit time.Duration) (*LimitedListener, error) {
if limit < 0 {
return nil, fmt.Errorf("Invalid time limit (negative)")
}
l, err := net.Listen("tcp", address)
if err != nil {
return nil, err
}
return &LimitedListener{
listener: l,
limit: limit,
lastConnection: time.Time{},
history: nil,
}, nil
}
// clearHistory truncates the history to only relevant entries
func (ll *LimitedListener) clearHistory() {
tl := time.Now().Add(-ll.limit) // get timelimit of range to check
// no connection made in the last X seconds
// the vast majority of connections will proc this
if ll.lastConnection.Before(tl) {
ll.history = nil // reset and release to gc
}
if len(ll.history) > 0 {
i := 0
for ; i < len(ll.history); i++ {
if ll.history[i].time.After(tl) { // inside target range
break
}
}
if i >= len(ll.history) {
ll.history = nil
} else {
ll.history = ll.history[i:]
}
}
}
// isInHistory checks if a host (IP) has connected in the last X seconds
// clears history before checking
func (ll *LimitedListener) isInHistory(host string) bool {
ll.clearHistory()
for _, h := range ll.history {
if h.host == host {
return true
}
}
return false
}
// addToHistory adds an address to the system at the current time
func (ll *LimitedListener) addToHistory(host string) {
ll.history = append(ll.history, attempt{host: host, time: time.Now()})
ll.lastConnection = time.Now()
}
// Accept accepts a connection if no other connection attempt from that ip has been made
// in the specified time frame
func (ll *LimitedListener) Accept() (net.Conn, error) {
//ll.listener.SetDeadline(time.Now().Add(time.Second))
con, err := ll.listener.Accept()
if err != nil {
return nil, err
}
host, _, err := net.SplitHostPort(con.RemoteAddr().String())
if err != nil {
con.Close()
return nil, err
}
if ll.isInHistory(host) {
con.Close()
return nil, fmt.Errorf("connection rate limit exceeded for %s", host)
}
ll.addToHistory(host)
return con, nil
}
// Addr returns the address the listener is listening to
func (ll *LimitedListener) Addr() net.Addr {
return ll.listener.Addr()
}
// Close closes the associated net.Listener
func (ll *LimitedListener) Close() {
ll.listener.Close()
}