feat(inputs.ipset): Add metric for number of entries and individual IPs (#16124)

This commit is contained in:
verybadsoldier 2024-12-27 15:34:52 +01:00 committed by GitHub
parent b92700f98a
commit 0c7c4242c6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 197 additions and 3 deletions

View File

@ -65,9 +65,10 @@ See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
## You can avoid using sudo or root, by setting appropriate privileges for
## the telegraf.service systemd service.
use_sudo = false
## Add number of entries and number of individual IPs (resolve CIDR syntax) for each ipset
count_per_ip_entries = false
## The default timeout of 1s for ipset execution can be overridden here:
# timeout = "1s"
```
## Metrics

View File

@ -28,8 +28,10 @@ type Ipset struct {
IncludeUnmatchedSets bool `toml:"include_unmatched_sets"`
UseSudo bool `toml:"use_sudo"`
Timeout config.Duration `toml:"timeout"`
CountPerIPEntries bool
lister setLister
lister setLister
entriesParser ipsetEntries
}
type setLister func(Timeout config.Duration, UseSudo bool) (*bytes.Buffer, error)
@ -56,6 +58,11 @@ func (i *Ipset) Gather(acc telegraf.Accumulator) error {
scanner := bufio.NewScanner(out)
for scanner.Scan() {
line := scanner.Text()
if i.CountPerIPEntries {
acc.AddError(i.entriesParser.addLine(line, acc))
}
// Ignore sets created without the "counters" option
nocomment := strings.Split(line, "\"")[0]
if !(strings.Contains(nocomment, "packets") &&
@ -101,6 +108,9 @@ func (i *Ipset) Gather(acc telegraf.Accumulator) error {
acc.AddCounter(measurement, fields, tags)
}
}
i.entriesParser.commit(acc)
return nil
}

View File

@ -0,0 +1,86 @@
//go:generate ../../../tools/readme_config_includer/generator
package ipset
import (
"errors"
"fmt"
"net"
"strings"
"github.com/influxdata/telegraf"
)
type ipsetEntries struct {
initialized bool
setName string
entries int
ips int
}
func getCountInCidr(cidr string) (int, error) {
_, ipNet, err := net.ParseCIDR(cidr)
if err != nil {
// check if single IP
if net.ParseIP(cidr) == nil {
return 0, errors.New("invalid IP address format. Not CIDR format and not a single IP address")
}
return 1, nil // Single IP has only one address
}
ones, bits := ipNet.Mask.Size()
if ones == 0 && bits == 0 {
return 0, errors.New("invalid CIDR range")
}
numIps := 1 << (bits - ones)
// exclude network and broadcast addresses if IPv4 and range > /31
if bits == 32 && numIps > 2 {
numIps -= 2
}
return numIps, nil
}
func (counter *ipsetEntries) addLine(line string, acc telegraf.Accumulator) error {
data := strings.Fields(line)
if len(data) < 3 {
return fmt.Errorf("error parsing line (expected at least 3 fields): %s", line)
}
switch data[0] {
case "create":
counter.commit(acc)
counter.initialized = true
counter.setName = data[1]
counter.entries = 0
counter.ips = 0
case "add":
counter.entries++
count, err := getCountInCidr(data[2])
if err != nil {
return err
}
counter.ips += count
}
return nil
}
func (counter *ipsetEntries) commit(acc telegraf.Accumulator) {
if !counter.initialized {
return
}
fields := map[string]interface{}{
"entries": counter.entries,
"ips": counter.ips,
}
tags := map[string]string{
"set": counter.setName,
}
acc.AddGauge(measurement, fields, tags)
// reset counter and prepare for next usage
counter.initialized = false
}

View File

@ -0,0 +1,96 @@
package ipset
import (
"testing"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
"github.com/stretchr/testify/require"
)
func TestIpsetEntries(t *testing.T) {
var acc testutil.Accumulator
lines := []string{
"create mylist hash:net family inet hashsize 16384 maxelem 131072 timeout 300 bucketsize 12 initval 0x4effa9ad",
"add mylist 89.101.238.143 timeout 161558",
"add mylist 122.224.15.166 timeout 186758",
"add mylist 47.128.40.145 timeout 431559",
}
entries := ipsetEntries{}
for _, line := range lines {
require.NoError(t, entries.addLine(line, &acc))
}
entries.commit(&acc)
expected := []telegraf.Metric{
testutil.MustMetric(
"ipset",
map[string]string{
"set": "mylist",
},
map[string]interface{}{
"entries": 3,
"ips": 3,
},
time.Unix(0, 0),
telegraf.Gauge,
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}
func TestIpsetEntriesCidr(t *testing.T) {
var acc testutil.Accumulator
lines := []string{
"create mylist0 hash:net family inet hashsize 16384 maxelem 131072 timeout 300 bucketsize 12 initval 0x4effa9ad",
"add mylist0 89.101.238.143 timeout 161558",
"add mylist0 122.224.5.0/24 timeout 186758",
"add mylist0 47.128.40.145 timeout 431559",
"create mylist1 hash:net family inet hashsize 16384 maxelem 131072 timeout 300 bucketsize 12 initval 0x4effa9ad",
"add mylist1 90.101.238.143 timeout 161558",
"add mylist1 44.128.40.145 timeout 431559",
"add mylist1 122.224.5.0/8 timeout 186758",
"add mylist1 45.128.40.145 timeout 431560",
}
entries := ipsetEntries{}
for _, line := range lines {
require.NoError(t, entries.addLine(line, &acc))
}
entries.commit(&acc)
expected := []telegraf.Metric{
testutil.MustMetric(
"ipset",
map[string]string{
"set": "mylist0",
},
map[string]interface{}{
"entries": 3,
"ips": 256,
},
time.Now().Add(time.Millisecond*0),
telegraf.Gauge,
),
testutil.MustMetric(
"ipset",
map[string]string{
"set": "mylist1",
},
map[string]interface{}{
"entries": 4,
"ips": 16777217,
},
time.Unix(0, 0),
telegraf.Gauge,
),
}
testutil.RequireMetricsEqual(t, expected, acc.GetTelegrafMetrics(), testutil.IgnoreTime())
}

View File

@ -7,6 +7,7 @@
## You can avoid using sudo or root, by setting appropriate privileges for
## the telegraf.service systemd service.
use_sudo = false
## Add number of entries and number of individual IPs (resolve CIDR syntax) for each ipset
count_per_ip_entries = false
## The default timeout of 1s for ipset execution can be overridden here:
# timeout = "1s"