Files
Blacktip/traffic.go
T

90 lines
2.3 KiB
Go

package blacktip
import (
"net"
"net/netip"
"sync"
"git.veillax.com/Veillax/reef/model"
)
// maxTrackedSources bounds the per-port table of client addresses between two reports, so a flood
// from spoofed or scanning addresses can't grow it without limit. Addresses past it still count
// in the port's totals; they just aren't named.
const maxTrackedSources = 5000
// sourceTable counts a port's traffic per client address until the next report takes it.
type sourceTable struct {
mu sync.Mutex
by map[string]*model.SourceStat
}
func (t *sourceTable) add(ip string, conns, in, out int64) {
if ip == "" {
return
}
t.mu.Lock()
defer t.mu.Unlock()
s := t.by[ip]
if s == nil {
if len(t.by) >= maxTrackedSources {
return
}
if t.by == nil {
t.by = map[string]*model.SourceStat{}
}
s = &model.SourceStat{IP: ip}
t.by[ip] = s
}
s.Conns += conns
s.BytesIn += in
s.BytesOut += out
}
func (t *sourceTable) take() map[string]*model.SourceStat {
t.mu.Lock()
defer t.mu.Unlock()
by := t.by
t.by = nil
return by
}
// clientIP reduces a "host:port" remote address to the client's IP (IPv4-mapped IPv6 as plain IPv4).
// Anything that isn't an address gives "".
func clientIP(remote string) string {
host, _, err := net.SplitHostPort(remote)
if err != nil {
host = remote
}
a, err := netip.ParseAddr(host)
if err != nil {
return ""
}
return a.WithZone("").Unmap().String()
}
// record counts traffic on a port, and names the client it came from when that is known.
func (ms *mapState) record(remote string, conns, in, out int64) {
ms.conns.Add(conns)
ms.bytesIn.Add(in)
ms.bytesOut.Add(out)
ms.sources.add(clientIP(remote), conns, in, out)
}
// takeStats hands over (and resets) the traffic counted since the last call.
func (ms *mapState) takeStats() (model.Stat, bool) {
st := model.Stat{Conns: ms.conns.Swap(0), BytesIn: ms.bytesIn.Swap(0), BytesOut: ms.bytesOut.Swap(0)}
st.Sources = model.TopSources(ms.sources.take(), model.MaxSources)
return st, st.Conns|st.BytesIn|st.BytesOut != 0
}
// restoreStats puts taken traffic back after a report that didn't arrive, so none of it is lost.
func (ms *mapState) restoreStats(st model.Stat) {
ms.conns.Add(st.Conns)
ms.bytesIn.Add(st.BytesIn)
ms.bytesOut.Add(st.BytesOut)
for _, s := range st.Sources {
ms.sources.add(s.IP, s.Conns, s.BytesIn, s.BytesOut)
}
}