-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathhooks.go
149 lines (126 loc) · 2.86 KB
/
hooks.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
package main
import (
"bytes"
"encoding/json"
"fmt"
"github.com/go-redis/redis"
"net"
"net/http"
"time"
)
type ipMap struct {
rwMutex
m map[string]bool
}
type hookService struct {
client http.Client
AddIPsUri string
RemoveIPsUri string
SyncIPsUri string
hookedIPs ipMap
redis *redis.Client
RedisAddr string
RedisChannel string
RedisPublishAdds bool
RedisPublishRemoves bool
}
func (h *hookService) init() error {
if h.RedisAddr != "" {
if h.RedisChannel == "" {
return fmt.Errorf("RedisChannel must be defined in config in order to publish hooks to redis.")
}
h.redis = redis.NewClient(&redis.Options{
Addr: h.RedisAddr,
DB: 0,
})
_, err := h.redis.Ping().Result()
if err != nil {
h.redis = nil
return err
}
}
return nil
}
func (h *hookService) sendHTTPHook(ips []net.IP, u string) error {
buf := new(bytes.Buffer)
err := json.NewEncoder(buf).Encode(ips)
if err != nil {
return err
}
req, err := http.NewRequest("POST", u, buf)
if err != nil {
return err
}
resp, err := h.client.Do(req)
if err != nil {
return err
}
resp.Body.Close()
return nil
}
func (h *hookService) Add(ipr ipRate) error {
if h.AddIPsUri != "" {
h.hookedIPs.Lock()
defer h.hookedIPs.Unlock()
// Short circuit already sent IPs
if h.hookedIPs.m[ipr.ip.String()] == true {
return nil
}
err := h.sendHTTPHook([]net.IP{*ipr.ip}, h.AddIPsUri)
if err != nil {
return err
}
h.hookedIPs.m[ipr.ip.String()] = true
}
if h.redis != nil && h.RedisPublishAdds {
// This info isn't necessarily set by the time we get the hook,
// so we're hacking it here.
// TODO... don't do this.
limitDuration := ipr.list.LimitDuration.multiply(float64(ipr.Strikes))
expire := time.Now().Add(limitDuration.Duration)
messageStruct := struct {
IP string `json:"ip"`
Expire time.Time `json:"expire"`
}{ipr.ip.String(), expire}
message, err := json.Marshal(messageStruct)
if err != nil {
return err
}
messageStr := string(message)
cmd := h.redis.Publish(h.RedisChannel, messageStr)
if err = cmd.Err(); err != nil {
return err
}
}
return nil
}
func (h *hookService) Remove(ipr ipRate) error {
if h.RemoveIPsUri != "" {
err := h.sendHTTPHook([]net.IP{*ipr.ip}, h.RemoveIPsUri)
if err != nil {
return err
}
h.hookedIPs.Lock()
defer h.hookedIPs.Unlock()
delete(h.hookedIPs.m, ipr.ip.String())
}
if h.RedisPublishRemoves {
return fmt.Errorf("hookService does not yet implement publishing ratelimit removes to redis.")
}
return nil
}
func (h *hookService) Sync(ips []net.IP) error {
if h.SyncIPsUri != "" {
err := h.sendHTTPHook(ips, h.SyncIPsUri)
if err != nil {
return err
}
h.hookedIPs.Lock()
defer h.hookedIPs.Unlock()
h.hookedIPs.m = make(map[string]bool)
for _, ip := range ips {
h.hookedIPs.m[ip.String()] = true
}
}
return nil
}