Skip to content

Commit

Permalink
emit metrics for iptables
Browse files Browse the repository at this point in the history
  • Loading branch information
blotus committed Jul 18, 2024
1 parent c34a688 commit 05de936
Showing 1 changed file with 125 additions and 34 deletions.
159 changes: 125 additions & 34 deletions pkg/iptables/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@
package iptables

import (
"bufio"
"os/exec"
"regexp"
"strconv"
"strings"
"time"

"github.com/prometheus/client_golang/prometheus"
log "github.com/sirupsen/logrus"

"github.com/crowdsecurity/cs-firewall-bouncer/pkg/metrics"

"github.com/prometheus/client_golang/prometheus"
)

type Ipsets struct {
Expand All @@ -24,72 +27,160 @@ type Ipsets struct {
} `xml:"ipset"`
}

func collectDroppedPackets(binaryPath string, chains []string, setName string) (float64, float64) {
//FIXME: do it per origin
var droppedPackets, droppedBytes float64
var chainRegexp = regexp.MustCompile(`^\[(\d+):(\d+)\]`)
var ruleRegexp = regexp.MustCompile(`^\[(\d+):(\d+)\] -A \w+ -m set --match-set (.*) src -j \w+`)

func (ctx *ipTablesContext) collectMetrics() (map[string]int, map[string]int, int, int, error) {
saveCmd := exec.Command(ctx.iptablesSaveBin, "-c", "-t", "filter")
out, err := saveCmd.CombinedOutput()
if err != nil {
log.Errorf("error while getting iptables rules : %v --> %s", err, string(out))
return nil, nil, 0, 0, err
}

processedBytes := 0
processedPackets := 0

droppedBytes := make(map[string]int)
droppedPackets := make(map[string]int)

scanner := bufio.NewScanner(strings.NewReader(string(out)))

for scanner.Scan() {
line := scanner.Text()

//Ignore chain declaration
if line[0] == ':' {
continue
}

//Jump to our chain, we can get the processed packets and bytes
if strings.Contains(line, "-j "+chainName) {
matches := chainRegexp.FindStringSubmatch(line)
if len(matches) != 3 {
log.Errorf("error while parsing counters : %s | not enough matches", line)
continue
}
val, err := strconv.Atoi(matches[1])
if err != nil {
log.Errorf("error while parsing counters : %s", line)
continue
}
processedPackets += val

val, err = strconv.Atoi(matches[2])
if err != nil {
log.Errorf("error while parsing counters : %s", line)
continue
}
processedBytes += val

for _, chain := range chains {
out, err := exec.Command(binaryPath, "-L", chain, "-v", "-x").CombinedOutput()
if err != nil {
log.Error(string(out), err)
continue
}

for _, line := range strings.Split(string(out), "\n") {
if !strings.Contains(line, setName) || strings.Contains(line, "LOG") {
//This is a rule
if strings.Contains(line, "-A "+chainName) {
matches := ruleRegexp.FindStringSubmatch(line)
if len(matches) != 4 {
log.Errorf("error while parsing counters : %s | not enough matches", line)
continue
}

parts := strings.Fields(line)
originIdStr, found := strings.CutPrefix(matches[3], ctx.SetName+"-")

Check failure on line 89 in pkg/iptables/metrics.go

View workflow job for this annotation

GitHub Actions / golangci-lint + codeql

var-naming: var originIdStr should be originIDStr (revive)
if !found {
log.Errorf("error while parsing counters : %s | no origin found", line)
continue
}
originId, err := strconv.Atoi(originIdStr)

Check failure on line 94 in pkg/iptables/metrics.go

View workflow job for this annotation

GitHub Actions / golangci-lint + codeql

var-naming: var originId should be originID (revive)

tdp, err := strconv.ParseFloat(parts[IPTablesDroppedPacketIdx], 64)
if err != nil {
log.Error(err.Error())
log.Errorf("error while parsing counters : %s | %s", line, err)
continue
}

if len(ctx.originSetMapping) < originId {
log.Errorf("Found unknown origin id : %d", originId)
continue
}

droppedPackets += tdp
origin := ctx.originSetMapping[originId]

tdb, err := strconv.ParseFloat(parts[IPTablesDroppedByteIdx], 64)
val, err := strconv.Atoi(matches[1])
if err != nil {
log.Error(err.Error())
log.Errorf("error while parsing counters : %s | %s", line, err)
continue
}
droppedPackets[origin] += val

val, err = strconv.Atoi(matches[2])
if err != nil {
log.Errorf("error while parsing counters : %s | %s", line, err)
continue
}

droppedBytes += tdb
droppedBytes[origin] += val
}
}

return droppedPackets, droppedBytes
log.Debugf("Processed %d packets and %d bytes", processedPackets, processedBytes)
log.Debugf("Dropped packets : %v", droppedPackets)
log.Debugf("Dropped bytes : %v", droppedBytes)

return droppedPackets, droppedBytes, processedPackets, processedBytes, nil
}

func (ipt *iptables) CollectMetrics() {
var ip4DroppedPackets, ip4DroppedBytes, ip6DroppedPackets, ip6DroppedBytes float64

t := time.NewTicker(metrics.MetricCollectionInterval)
for range t.C {
if ipt.v4 != nil && !ipt.v4.ipsetContentOnly {
ip4DroppedPackets, ip4DroppedBytes = collectDroppedPackets(ipt.v4.iptablesBin, ipt.v4.Chains, ipt.v4.SetName)
}

if ipt.v6 != nil && !ipt.v6.ipsetContentOnly {
ip6DroppedPackets, ip6DroppedBytes = collectDroppedPackets(ipt.v6.iptablesBin, ipt.v6.Chains, ipt.v6.SetName)
}

if (ipt.v4 != nil && !ipt.v4.ipsetContentOnly) || (ipt.v6 != nil && !ipt.v6.ipsetContentOnly) {
//FIXME: origin
metrics.TotalDroppedPackets.With(prometheus.Labels{"ip_type": "ipv4", "origin": ""}).Set(ip4DroppedPackets + ip6DroppedPackets)
metrics.TotalDroppedBytes.With(prometheus.Labels{"ip_type": "ipv4", "origin": ""}).Set(ip6DroppedBytes + ip4DroppedBytes)
}

if ipt.v4 != nil {
for origin, set := range ipt.v4.ipsets {
metrics.TotalActiveBannedIPs.With(prometheus.Labels{"ip_type": "ipv4", "origin": origin}).Set(float64(set.Len()))
}
if !ipt.v4.ipsetContentOnly {
ipv4DroppedPackets, ipv4DroppedBytes, ipv4ProcessedPackets, ipv4ProcessedBytes, err := ipt.v4.collectMetrics()

if err != nil {
log.Errorf("can't collect dropped packets for ipv4 from iptables: %s", err)
continue
}

metrics.TotalProcessedPackets.With(prometheus.Labels{"ip_type": "ipv4"}).Set(float64(ipv4ProcessedPackets))
metrics.TotalProcessedBytes.With(prometheus.Labels{"ip_type": "ipv4"}).Set(float64(ipv4ProcessedBytes))

for origin, count := range ipv4DroppedPackets {
metrics.TotalDroppedPackets.With(prometheus.Labels{"ip_type": "ipv4", "origin": origin}).Set(float64(count))
}

for origin, count := range ipv4DroppedBytes {
metrics.TotalDroppedBytes.With(prometheus.Labels{"ip_type": "ipv4", "origin": origin}).Set(float64(count))
}

}
}

if ipt.v6 != nil {
for origin, set := range ipt.v6.ipsets {
metrics.TotalActiveBannedIPs.With(prometheus.Labels{"ip_type": "ipv6", "origin": origin}).Set(float64(set.Len()))
}
if !ipt.v6.ipsetContentOnly {
ipv6DroppedPackets, ipv6DroppedBytes, ipv6ProcessedPackets, ipv6ProcessedBytes, err := ipt.v6.collectMetrics()

if err != nil {
log.Errorf("can't collect dropped packets for ipv6 from iptables: %s", err)
continue
}

metrics.TotalProcessedPackets.With(prometheus.Labels{"ip_type": "ipv6"}).Set(float64(ipv6ProcessedPackets))
metrics.TotalProcessedBytes.With(prometheus.Labels{"ip_type": "ipv6"}).Set(float64(ipv6ProcessedBytes))

for origin, count := range ipv6DroppedPackets {
metrics.TotalDroppedPackets.With(prometheus.Labels{"ip_type": "ipv6", "origin": origin}).Set(float64(count))
}

for origin, count := range ipv6DroppedBytes {
metrics.TotalDroppedBytes.With(prometheus.Labels{"ip_type": "ipv6", "origin": origin}).Set(float64(count))
}
}
}
}
}

0 comments on commit 05de936

Please sign in to comment.