/
iptrk.go
315 lines (247 loc) · 5.32 KB
/
iptrk.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
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
package pitchfork
/*
* Note: iptrk uses non-audit versions of DB queries, otherwise we would generate double traffic
*
* IP tracking is done in a DB so that it is distributed between nodes
*/
import (
"errors"
"time"
)
type IPtrkEntry struct {
Blocked bool
IP string
Count int
Entered time.Time
Last time.Time
}
type IPtrkS struct {
cmd string
ip string
chn chan bool
}
var IPtrk_Max int
var IPtrk chan IPtrkS
var IPtrk_done chan bool
var IPtrk_running bool
func iptrk_add(ip string) (ret bool) {
ret = true
cnt := 0
/* Add a new hit */
/*
* TODO: Postgres 9.5+
*
* q := "INSERT INTO iptrk (ip) VALUES($1) " +
* "ON CONFLICT (ip) " +
* "DO UPDATE SET count = iptrk.count + EXCLUDED.count, last = NOW() " +
* "RETURNING count"
* err := DB.QueryRowNA(q, ip).Scan(&cnt)
*/
q := "INSERT INTO iptrk " +
"(ip) " +
"VALUES($1) " +
"RETURNING count"
err := DB.QueryRowNA(q, ip).Scan(&cnt)
if err != nil && DB_IsPQErrorConstraint(err) {
q = "UPDATE iptrk " +
"SET count = count + 1 " +
"WHERE ip = $1 " +
"RETURNING count"
err = DB.QueryRowNA(q, ip).Scan(&cnt)
}
if err != nil {
Errf("Chk: %q %v %q", q, ip, err.Error())
return
}
q = "SELECT count " +
"FROM iptrk " +
"WHERE ip = $1"
err = DB.QueryRow(q, ip).Scan(&cnt)
if err != nil {
Errf("Chk: %q %v %q", q, ip, err.Error())
return
}
/* Below the limit? */
if cnt <= IPtrk_Max {
/* Not blocked */
ret = false
}
return
}
func iptrk_expire(t string) bool {
Dbgf("Expiring")
/* Expire tracking */
q := "DELETE FROM iptrk WHERE last < (NOW() - INTERVAL '" + t + "')"
err := DB.ExecNA(-1, q)
if err != nil {
Errf("ExpireTrk: %s", err.Error())
}
return true
}
func iptrk_flush(ip string) bool {
var err error
if ip == "" {
/* Flush the whole IP Tracking table */
q := "DELETE FROM iptrk"
err = DB.ExecNA(-1, q)
} else {
/* Flush only a single IP */
q := "DELETE FROM iptrk WHERE ip = $1"
err = DB.ExecNA(-1, q, ip)
}
if err != nil {
Errf("iptrk_flush() failed: %s", err.Error())
return false
}
return true
}
/* Go routine that manages the ip tracking */
func iptrk_rtn(timeoutchk time.Duration, expire string) {
IPtrk_running = true
/* Timer for expiring entries */
tmr_exp := time.NewTimer(timeoutchk)
for IPtrk_running {
select {
case s, ok := <-IPtrk:
if !ok {
IPtrk_running = false
break
}
ret := true
switch s.cmd {
case "add":
ret = iptrk_add(s.ip)
break
case "wipe":
ret = iptrk_expire(expire)
break
case "flush":
ret = iptrk_flush(s.ip)
break
default:
panic("Unhandled cmd: " + s.cmd)
}
/* Return */
s.chn <- ret
break
case <-tmr_exp.C:
Dbgf("Timer: Expire")
iptrk_expire(expire)
/* Restart timer */
tmr_exp = time.NewTimer(timeoutchk)
break
}
}
IPtrk_done <- true
}
func iptrk_cmd(cmd string, ip string) (ret bool) {
/* Create result channel */
chn := make(chan bool)
IPtrk <- IPtrkS{cmd, ip, chn}
/* Wait for result */
ret = <-chn
return
}
func Iptrk_count(ip string) (limited bool) {
if IPtrk_running {
limited = iptrk_cmd("add", ip)
} else {
limited = iptrk_add(ip)
}
return
}
func Iptrk_start(max int, timeoutchk time.Duration, expire string) {
IPtrk = make(chan IPtrkS, 1000)
IPtrk_done = make(chan bool)
IPtrk_Max = max
go iptrk_rtn(timeoutchk, expire)
}
func Iptrk_stop() {
if !IPtrk_running {
return
}
/* Close the channel */
close(IPtrk)
/* Wait for it to finish */
<-IPtrk_done
}
func Iptrk_reset(ip string) (ret bool) {
if IPtrk_running {
ret = iptrk_cmd("flush", ip)
} else {
ret = iptrk_flush(ip)
}
return
}
func IPtrk_List(ctx PfCtx) (ts []IPtrkEntry, err error) {
q := "SELECT " +
"ip, count, entered, last " +
"FROM iptrk " +
"ORDER BY ip"
rows, err := DB.Query(q)
if err != nil {
return
}
defer rows.Close()
for rows.Next() {
var t IPtrkEntry
err = rows.Scan(&t.IP, &t.Count, &t.Entered, &t.Last)
if err != nil {
return
}
t.Blocked = t.Count > IPtrk_Max
ts = append(ts, t)
}
if len(ts) == 0 {
err = ErrNoRows
}
return
}
func iptrk_list(ctx PfCtx, args []string) (err error) {
ts, err := IPtrk_List(ctx)
if err == ErrNoRows {
ctx.OutLn("There are currently no entries")
err = nil
return
}
if err != nil {
return
}
ctx.Outf("%16s %16s %7s %10s %s\n", "Entered", "Last", "Status", "Count", "IP")
for _, t := range ts {
s := "okay"
if t.Blocked {
s = "blocked"
}
ctx.Outf("%16s %16s %7s %10d %s\n", Fmt_Time(t.Entered), Fmt_Time(t.Last), s, t.Count, t.IP)
}
return
}
func iptrk_flushcmd(ctx PfCtx, args []string) (err error) {
Iptrk_reset("")
ctx.OutLn("IPtrk flushed")
return
}
func iptrk_remove(ctx PfCtx, args []string) (err error) {
ip := args[0]
if ip == "" {
err = errors.New("Missing argument, IP address required")
return
}
ret := Iptrk_reset(ip)
if ret {
ctx.OutLn("IP removed from IPtrk table")
} else {
ctx.OutLn("No such IP in IPtrk table")
}
return
}
func iptrk_menu(ctx PfCtx, args []string) (err error) {
menu := NewPfMenu([]PfMEntry{
{"list", iptrk_list, 0, 0, nil, PERM_SYS_ADMIN, "List the contents of the IPtrk tables"},
{"flush", iptrk_flushcmd, 0, 0, nil, PERM_SYS_ADMIN, "Flush all entries from the IPtrk table"},
{"remove", iptrk_remove, 1, 1, []string{"ip"}, PERM_SYS_ADMIN, "Remove an entry from IPtrk"},
})
err = ctx.Menu(args, menu)
return
}