Skip to content

Commit 3f592b5

Browse files
authored
Add support for usage metrics + improve metrics performance + improve iptables mode performance (#365)
1 parent 9594360 commit 3f592b5

19 files changed

+1472
-817
lines changed

.github/workflows/build-binary-package.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ jobs:
2727
- name: Set up Go
2828
uses: actions/setup-go@v5
2929
with:
30-
go-version: 1.22.5
30+
go-version: '1.22'
3131

3232
- name: Build all platforms
3333
run: |

.github/workflows/lint.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ jobs:
2525
- name: Set up Go
2626
uses: actions/setup-go@v5
2727
with:
28-
go-version: 1.22.5
28+
go-version: '1.22'
2929

3030
- name: Initialize CodeQL
3131
uses: github/codeql-action/init@v3

.github/workflows/tests.yml

+6-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,12 @@ jobs:
2424
- name: Set up Go
2525
uses: actions/setup-go@v5
2626
with:
27-
go-version: 1.22.5
27+
go-version: '1.22'
28+
29+
- name: mod tidy
30+
run: |
31+
go mod tidy
32+
git diff
2833
2934
- name: Build
3035
run: |

.github/workflows/tests_deb.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
- name: Set up Go
2525
uses: actions/setup-go@v5
2626
with:
27-
go-version: 1.22.5
27+
go-version: '1.22'
2828

2929
- name: Cache virtualenvs
3030
id: cache-pipenv

cmd/root.go

+156-15
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ import (
99
"net/http"
1010
"os"
1111
"os/signal"
12+
"slices"
1213
"strings"
1314
"syscall"
15+
"time"
1416

1517
"github.com/prometheus/client_golang/prometheus"
1618
"github.com/prometheus/client_golang/prometheus/promhttp"
19+
io_prometheus_client "github.com/prometheus/client_model/go"
1720
log "github.com/sirupsen/logrus"
18-
"golang.org/x/exp/slices"
1921
"golang.org/x/sync/errgroup"
2022

2123
csbouncer "github.com/crowdsecurity/go-cs-bouncer"
2224
"github.com/crowdsecurity/go-cs-lib/csdaemon"
2325
"github.com/crowdsecurity/go-cs-lib/csstring"
26+
"github.com/crowdsecurity/go-cs-lib/ptr"
2427
"github.com/crowdsecurity/go-cs-lib/version"
2528

2629
"github.com/crowdsecurity/crowdsec/pkg/models"
@@ -30,7 +33,11 @@ import (
3033
"github.com/crowdsecurity/cs-firewall-bouncer/pkg/metrics"
3134
)
3235

33-
const name = "crowdsec-firewall-bouncer"
36+
const bouncerType = "crowdsec-firewall-bouncer"
37+
38+
type metricsHandler struct {
39+
backend *backend.BackendCTX
40+
}
3441

3542
func backendCleanup(backend *backend.BackendCTX) {
3643
log.Info("Shutting down backend")
@@ -136,6 +143,134 @@ func addDecisions(backend *backend.BackendCTX, decisions []*models.Decision, con
136143
}
137144
}
138145

146+
func getLabelValue(labels []*io_prometheus_client.LabelPair, key string) string {
147+
148+
for _, label := range labels {
149+
if label.GetName() == key {
150+
return label.GetValue()
151+
}
152+
}
153+
154+
return ""
155+
}
156+
157+
// metricsUpdater receives a metrics struct with basic data and populates it with the current metrics.
158+
func (m metricsHandler) metricsUpdater(met *models.RemediationComponentsMetrics, updateInterval time.Duration) {
159+
log.Debugf("Updating metrics")
160+
161+
m.backend.CollectMetrics()
162+
163+
//Most of the common fields are set automatically by the metrics provider
164+
//We only need to care about the metrics themselves
165+
166+
promMetrics, err := prometheus.DefaultGatherer.Gather()
167+
168+
if err != nil {
169+
log.Errorf("unable to gather prometheus metrics: %s", err)
170+
return
171+
}
172+
173+
met.Metrics = append(met.Metrics, &models.DetailedMetrics{
174+
Meta: &models.MetricsMeta{
175+
UtcNowTimestamp: ptr.Of(time.Now().Unix()),
176+
WindowSizeSeconds: ptr.Of(int64(updateInterval.Seconds())),
177+
},
178+
Items: make([]*models.MetricsDetailItem, 0),
179+
})
180+
181+
for _, metricFamily := range promMetrics {
182+
for _, metric := range metricFamily.GetMetric() {
183+
switch metricFamily.GetName() {
184+
case metrics.ActiveBannedIPsMetricName:
185+
//We send the absolute value, as it makes no sense to try to sum them crowdsec side
186+
labels := metric.GetLabel()
187+
value := metric.GetGauge().GetValue()
188+
origin := getLabelValue(labels, "origin")
189+
ipType := getLabelValue(labels, "ip_type")
190+
log.Debugf("Sending active decisions for %s %s | current value: %f", origin, ipType, value)
191+
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
192+
Name: ptr.Of("active_decisions"),
193+
Value: ptr.Of(value),
194+
Labels: map[string]string{
195+
"origin": origin,
196+
"ip_type": ipType,
197+
},
198+
Unit: ptr.Of("ip"),
199+
})
200+
case metrics.DroppedBytesMetricName:
201+
labels := metric.GetLabel()
202+
value := metric.GetGauge().GetValue()
203+
origin := getLabelValue(labels, "origin")
204+
ipType := getLabelValue(labels, "ip_type")
205+
key := origin + ipType
206+
log.Debugf("Sending dropped bytes for %s %s %f | current value: %f | previous value: %f\n", origin, ipType, value-metrics.LastDroppedBytesValue[key], value, metrics.LastDroppedBytesValue[key])
207+
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
208+
Name: ptr.Of("dropped"),
209+
Value: ptr.Of(value - metrics.LastDroppedBytesValue[key]),
210+
Labels: map[string]string{
211+
"origin": origin,
212+
"ip_type": ipType,
213+
},
214+
Unit: ptr.Of("byte"),
215+
})
216+
metrics.LastDroppedBytesValue[key] = value
217+
case metrics.DroppedPacketsMetricName:
218+
labels := metric.GetLabel()
219+
value := metric.GetGauge().GetValue()
220+
origin := getLabelValue(labels, "origin")
221+
ipType := getLabelValue(labels, "ip_type")
222+
key := origin + ipType
223+
log.Debugf("Sending dropped packets for %s %s %f | current value: %f | previous value: %f\n", origin, ipType, value-metrics.LastDroppedPacketsValue[key], value, metrics.LastDroppedPacketsValue[key])
224+
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
225+
Name: ptr.Of("dropped"),
226+
Value: ptr.Of(value - metrics.LastDroppedPacketsValue[key]),
227+
Labels: map[string]string{
228+
"origin": origin,
229+
"ip_type": ipType,
230+
},
231+
Unit: ptr.Of("packet"),
232+
})
233+
metrics.LastDroppedPacketsValue[key] = value
234+
case metrics.ProcessedBytesMetricName:
235+
labels := metric.GetLabel()
236+
value := metric.GetGauge().GetValue()
237+
ipType := getLabelValue(labels, "ip_type")
238+
log.Debugf("Sending processed bytes for %s %f | current value: %f | previous value: %f\n", ipType, value-metrics.LastProcessedBytesValue[ipType], value, metrics.LastProcessedBytesValue[ipType])
239+
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
240+
Name: ptr.Of("processed"),
241+
Value: ptr.Of(value - metrics.LastProcessedBytesValue[ipType]),
242+
Labels: map[string]string{
243+
"ip_type": ipType,
244+
},
245+
Unit: ptr.Of("byte"),
246+
})
247+
metrics.LastProcessedBytesValue[ipType] = value
248+
case metrics.ProcessedPacketsMetricName:
249+
labels := metric.GetLabel()
250+
value := metric.GetGauge().GetValue()
251+
ipType := getLabelValue(labels, "ip_type")
252+
log.Debugf("Sending processed packets for %s %f | current value: %f | previous value: %f\n", ipType, value-metrics.LastProcessedPacketsValue[ipType], value, metrics.LastProcessedPacketsValue[ipType])
253+
met.Metrics[0].Items = append(met.Metrics[0].Items, &models.MetricsDetailItem{
254+
Name: ptr.Of("processed"),
255+
Value: ptr.Of(value - metrics.LastProcessedPacketsValue[ipType]),
256+
Labels: map[string]string{
257+
"ip_type": ipType,
258+
},
259+
Unit: ptr.Of("packet"),
260+
})
261+
metrics.LastProcessedPacketsValue[ipType] = value
262+
}
263+
}
264+
}
265+
}
266+
267+
func (m metricsHandler) computeMetricsHandler(next http.Handler) http.Handler {
268+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
269+
m.backend.CollectMetrics()
270+
next.ServeHTTP(w, r)
271+
})
272+
}
273+
139274
func Execute() error {
140275
configPath := flag.String("c", "", "path to crowdsec-firewall-bouncer.yaml")
141276
verbose := flag.Bool("v", false, "set verbose mode")
@@ -176,7 +311,7 @@ func Execute() error {
176311
log.SetLevel(log.DebugLevel)
177312
}
178313

179-
log.Infof("Starting crowdsec-firewall-bouncer %s", version.String())
314+
log.Infof("Starting %s %s", bouncerType, version.String())
180315

181316
backend, err := backend.NewBackend(config)
182317
if err != nil {
@@ -196,7 +331,7 @@ func Execute() error {
196331
return err
197332
}
198333

199-
bouncer.UserAgent = fmt.Sprintf("%s/%s", name, version.String())
334+
bouncer.UserAgent = fmt.Sprintf("%s/%s", bouncerType, version.String())
200335
if err := bouncer.Init(); err != nil {
201336
return fmt.Errorf("unable to configure bouncer: %w", err)
202337
}
@@ -217,21 +352,27 @@ func Execute() error {
217352
return errors.New("bouncer stream halted")
218353
})
219354

220-
if config.PrometheusConfig.Enabled {
221-
if config.Mode == cfg.IptablesMode || config.Mode == cfg.NftablesMode || config.Mode == cfg.IpsetMode || config.Mode == cfg.PfMode {
222-
go backend.CollectMetrics()
355+
mHandler := metricsHandler{
356+
backend: backend,
357+
}
223358

224-
if config.Mode == cfg.IpsetMode {
225-
prometheus.MustRegister(metrics.TotalActiveBannedIPs)
226-
} else {
227-
prometheus.MustRegister(metrics.TotalDroppedBytes, metrics.TotalDroppedPackets, metrics.TotalActiveBannedIPs)
228-
}
229-
}
359+
metricsProvider, err := csbouncer.NewMetricsProvider(bouncer.APIClient, bouncerType, mHandler.metricsUpdater, log.StandardLogger())
360+
if err != nil {
361+
return fmt.Errorf("unable to create metrics provider: %w", err)
362+
}
230363

231-
prometheus.MustRegister(csbouncer.TotalLAPICalls, csbouncer.TotalLAPIError)
364+
g.Go(func() error {
365+
return metricsProvider.Run(ctx)
366+
})
367+
368+
if config.Mode == cfg.IptablesMode || config.Mode == cfg.NftablesMode || config.Mode == cfg.IpsetMode || config.Mode == cfg.PfMode {
369+
prometheus.MustRegister(metrics.TotalDroppedBytes, metrics.TotalDroppedPackets, metrics.TotalActiveBannedIPs, metrics.TotalProcessedBytes, metrics.TotalProcessedPackets)
370+
}
232371

372+
prometheus.MustRegister(csbouncer.TotalLAPICalls, csbouncer.TotalLAPIError)
373+
if config.PrometheusConfig.Enabled {
233374
go func() {
234-
http.Handle("/metrics", promhttp.Handler())
375+
http.Handle("/metrics", mHandler.computeMetricsHandler(promhttp.Handler()))
235376

236377
listenOn := net.JoinHostPort(
237378
config.PrometheusConfig.ListenAddress,

go.mod

+37-35
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,63 @@
11
module github.com/crowdsecurity/cs-firewall-bouncer
22

3-
go 1.21
3+
go 1.22
44

55
require (
6-
github.com/crowdsecurity/crowdsec v1.6.1
7-
github.com/crowdsecurity/go-cs-bouncer v0.0.13
8-
github.com/crowdsecurity/go-cs-lib v0.0.10
9-
github.com/google/nftables v0.1.1-0.20230710063801-8a10f689006b
10-
github.com/prometheus/client_golang v1.17.0
6+
github.com/crowdsecurity/crowdsec v1.6.3-rc3
7+
github.com/crowdsecurity/go-cs-bouncer v0.0.14-0.20240819095913-4521d8ddc0c6
8+
github.com/crowdsecurity/go-cs-lib v0.0.13
9+
github.com/google/nftables v0.2.0
10+
github.com/prometheus/client_golang v1.20.0
11+
github.com/prometheus/client_model v0.6.1
1112
github.com/sirupsen/logrus v1.9.3
12-
github.com/stretchr/testify v1.8.4
13-
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1
14-
golang.org/x/sync v0.6.0
15-
golang.org/x/sys v0.19.0
13+
github.com/stretchr/testify v1.9.0
14+
golang.org/x/sync v0.8.0
15+
golang.org/x/sys v0.24.0
1616
gopkg.in/natefinch/lumberjack.v2 v2.2.1
1717
gopkg.in/yaml.v2 v2.4.0
1818
)
1919

2020
require (
21-
github.com/antonmedv/expr v1.15.3 // indirect
2221
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
2322
github.com/beorn7/perks v1.0.1 // indirect
24-
github.com/cespare/xxhash/v2 v2.2.0 // indirect
23+
github.com/blackfireio/osinfo v1.0.5 // indirect
24+
github.com/cespare/xxhash/v2 v2.3.0 // indirect
2525
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
2626
github.com/davecgh/go-spew v1.1.1 // indirect
27-
github.com/fatih/color v1.15.0 // indirect
28-
github.com/go-openapi/analysis v0.21.4 // indirect
29-
github.com/go-openapi/errors v0.20.4 // indirect
30-
github.com/go-openapi/jsonpointer v0.20.0 // indirect
31-
github.com/go-openapi/jsonreference v0.20.2 // indirect
32-
github.com/go-openapi/loads v0.21.2 // indirect
33-
github.com/go-openapi/spec v0.20.9 // indirect
34-
github.com/go-openapi/strfmt v0.21.7 // indirect
35-
github.com/go-openapi/swag v0.22.4 // indirect
36-
github.com/go-openapi/validate v0.22.1 // indirect
37-
github.com/goccy/go-yaml v1.11.2 // indirect
27+
github.com/expr-lang/expr v1.16.9 // indirect
28+
github.com/fatih/color v1.17.0 // indirect
29+
github.com/go-openapi/analysis v0.23.0 // indirect
30+
github.com/go-openapi/errors v0.22.0 // indirect
31+
github.com/go-openapi/jsonpointer v0.21.0 // indirect
32+
github.com/go-openapi/jsonreference v0.21.0 // indirect
33+
github.com/go-openapi/loads v0.22.0 // indirect
34+
github.com/go-openapi/spec v0.21.0 // indirect
35+
github.com/go-openapi/strfmt v0.23.0 // indirect
36+
github.com/go-openapi/swag v0.23.0 // indirect
37+
github.com/go-openapi/validate v0.24.0 // indirect
38+
github.com/goccy/go-yaml v1.12.0 // indirect
3839
github.com/golang-jwt/jwt/v4 v4.5.0 // indirect
39-
github.com/google/go-cmp v0.5.9 // indirect
40+
github.com/google/go-cmp v0.6.0 // indirect
4041
github.com/google/go-querystring v1.1.0 // indirect
42+
github.com/google/uuid v1.6.0 // indirect
4143
github.com/josharian/intern v1.0.0 // indirect
42-
github.com/josharian/native v1.0.0 // indirect
44+
github.com/josharian/native v1.1.0 // indirect
45+
github.com/klauspost/compress v1.17.9 // indirect
4346
github.com/mailru/easyjson v0.7.7 // indirect
4447
github.com/mattn/go-colorable v0.1.13 // indirect
4548
github.com/mattn/go-isatty v0.0.20 // indirect
46-
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
47-
github.com/mdlayher/netlink v1.7.1 // indirect
48-
github.com/mdlayher/socket v0.4.0 // indirect
49+
github.com/mdlayher/netlink v1.7.2 // indirect
50+
github.com/mdlayher/socket v0.5.1 // indirect
4951
github.com/mitchellh/mapstructure v1.5.0 // indirect
52+
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
5053
github.com/oklog/ulid v1.3.1 // indirect
5154
github.com/pmezard/go-difflib v1.0.0 // indirect
52-
github.com/prometheus/client_model v0.5.0 // indirect
53-
github.com/prometheus/common v0.45.0 // indirect
54-
github.com/prometheus/procfs v0.12.0 // indirect
55-
go.mongodb.org/mongo-driver v1.12.1 // indirect
56-
golang.org/x/net v0.24.0 // indirect
57-
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
58-
google.golang.org/protobuf v1.33.0 // indirect
55+
github.com/prometheus/common v0.55.0 // indirect
56+
github.com/prometheus/procfs v0.15.1 // indirect
57+
go.mongodb.org/mongo-driver v1.16.1 // indirect
58+
golang.org/x/net v0.28.0 // indirect
59+
golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect
60+
google.golang.org/protobuf v1.34.2 // indirect
5961
gopkg.in/tomb.v2 v2.0.0-20161208151619-d5d1b5820637 // indirect
6062
gopkg.in/yaml.v3 v3.0.1 // indirect
6163
)

0 commit comments

Comments
 (0)