From 3dd68cd8e6372b4b6c919dbfe9320e9b5ac93876 Mon Sep 17 00:00:00 2001 From: Hector Date: Sun, 29 Aug 2021 17:33:12 +0100 Subject: [PATCH] collect metrics on jail stats Collect more stats on each jail (number of current/total failures, and number of current/total bans). This is the same data shown by the client when running the `status` command. Add new metrics to export the new data. --- src/exporter.go | 54 ++++++++++++++++++++++++++++++++-- src/socket/fail2banSocket.go | 56 ++++++++++++++++++++++++++++++++++++ 2 files changed, 108 insertions(+), 2 deletions(-) diff --git a/src/exporter.go b/src/exporter.go index 6dd422f..146eab4 100644 --- a/src/exporter.go +++ b/src/exporter.go @@ -49,6 +49,7 @@ var ( "Number of errors found since startup.", []string{"type"}, nil, ) + metricServerPing = prometheus.NewDesc( prometheus.BuildFQName(sockNamespace, "", "up"), "Check if the fail2ban server is up", @@ -59,6 +60,26 @@ var ( "Number of defined jails", nil, nil, ) + metricJailFailedCurrent = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_failed_current"), + "Number of current failures on this jail's filter", + []string{"jail"}, nil, + ) + metricJailFailedTotal = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_failed_total"), + "Number of total failures on this jail's filter", + []string{"jail"}, nil, + ) + metricJailBannedCurrent = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_banned_current"), + "Number of IPs currently banned in this jail", + []string{"jail"}, nil, + ) + metricJailBannedTotal = prometheus.NewDesc( + prometheus.BuildFQName(sockNamespace, "", "jail_banned_total"), + "Total number of IPs banned by this jail (includes expired bans)", + []string{"jail"}, nil, + ) ) type Exporter struct { @@ -79,6 +100,10 @@ func (e *Exporter) Describe(ch chan<- *prometheus.Desc) { if e.socket != nil { ch <- metricServerPing ch <- metricJailCount + ch <- metricJailFailedCurrent + ch <- metricJailFailedTotal + ch <- metricJailBannedCurrent + ch <- metricJailBannedTotal } } @@ -92,7 +117,7 @@ func (e *Exporter) Collect(ch chan<- prometheus.Metric) { } if e.socket != nil { e.collectServerPingMetric(ch) - e.collectJailCountMetric(ch) + e.collectJailMetrics(ch) } } @@ -171,7 +196,7 @@ func (e *Exporter) collectServerPingMetric(ch chan<- prometheus.Metric) { ) } -func (e *Exporter) collectJailCountMetric(ch chan<- prometheus.Metric) { +func (e *Exporter) collectJailMetrics(ch chan<- prometheus.Metric) { jails, err := e.socket.GetJails() var count float64 = 0 if err == nil { @@ -180,6 +205,31 @@ func (e *Exporter) collectJailCountMetric(ch chan<- prometheus.Metric) { ch <- prometheus.MustNewConstMetric( metricJailCount, prometheus.GaugeValue, count, ) + + for i := range(jails) { + e.collectJailStatsMetric(ch, jails[i]) + } +} + +func (e *Exporter) collectJailStatsMetric(ch chan<- prometheus.Metric, jail string) { + stats, err := e.socket.GetJailStats(jail) + if err != nil { + log.Printf("failed to get stats for jail %s: %v", jail, err) + return + } + + ch <- prometheus.MustNewConstMetric( + metricJailFailedCurrent, prometheus.GaugeValue, float64(stats.FailedCurrent), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailFailedTotal, prometheus.GaugeValue, float64(stats.FailedTotal), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailBannedCurrent, prometheus.GaugeValue, float64(stats.BannedCurrent), jail, + ) + ch <- prometheus.MustNewConstMetric( + metricJailBannedTotal, prometheus.GaugeValue, float64(stats.BannedTotal), jail, + ) } func printAppVersion() { diff --git a/src/socket/fail2banSocket.go b/src/socket/fail2banSocket.go index 48e2ad6..61d273d 100644 --- a/src/socket/fail2banSocket.go +++ b/src/socket/fail2banSocket.go @@ -14,6 +14,13 @@ type Fail2BanSocket struct { encoder *ogórek.Encoder } +type JailStats struct { + FailedCurrent int + FailedTotal int + BannedCurrent int + BannedTotal int +} + func MustConnectToSocket(path string) *Fail2BanSocket { c, err := net.Dial("unix", path) if err != nil { @@ -61,6 +68,55 @@ func (s *Fail2BanSocket) GetJails() ([]string, error) { return nil, newBadFormatError(statusCommand, response) } +func (s *Fail2BanSocket) GetJailStats(jail string) (JailStats, error) { + response, err := s.sendCommand([]string{statusCommand, jail}) + if err != nil { + return JailStats{}, err + } + + stats := JailStats{ + FailedCurrent: -1, + FailedTotal: -1, + BannedCurrent: -1, + BannedTotal: -1, + } + + if lvl1, ok := response.(*types.Tuple); ok { + if lvl2, ok := lvl1.Get(1).(*types.List); ok { + if filter, ok := lvl2.Get(0).(*types.Tuple); ok { + if filterLvl1, ok := filter.Get(1).(*types.List); ok { + if filterCurrentTuple, ok := filterLvl1.Get(0).(*types.Tuple); ok { + if filterCurrent, ok := filterCurrentTuple.Get(1).(int); ok { + stats.FailedCurrent = filterCurrent + } + } + if filterTotalTuple, ok := filterLvl1.Get(1).(*types.Tuple); ok { + if filterTotal, ok := filterTotalTuple.Get(1).(int); ok { + stats.FailedTotal = filterTotal + } + } + } + } + if actions, ok := lvl2.Get(1).(*types.Tuple); ok { + if actionsLvl1, ok := actions.Get(1).(*types.List); ok { + if actionsCurrentTuple, ok := actionsLvl1.Get(0).(*types.Tuple); ok { + if actionsCurrent, ok := actionsCurrentTuple.Get(1).(int); ok { + stats.BannedCurrent = actionsCurrent + } + } + if actionsTotalTuple, ok := actionsLvl1.Get(1).(*types.Tuple); ok { + if actionsTotal, ok := actionsTotalTuple.Get(1).(int); ok { + stats.BannedTotal = actionsTotal + } + } + } + } + return stats, nil + } + } + return stats, newBadFormatError(statusCommand, response) +} + func newBadFormatError(command string, data interface{}) error { return fmt.Errorf("(%s) unexpected response format - cannot parse: %v", command, data) }