diff --git a/plugins/inputs/ipset/README.md b/plugins/inputs/ipset/README.md index 756a426081c8e..0811bf870f75b 100644 --- a/plugins/inputs/ipset/README.md +++ b/plugins/inputs/ipset/README.md @@ -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 diff --git a/plugins/inputs/ipset/ipset.go b/plugins/inputs/ipset/ipset.go index be177d10a26ac..1d13de9cb6ff9 100644 --- a/plugins/inputs/ipset/ipset.go +++ b/plugins/inputs/ipset/ipset.go @@ -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 } diff --git a/plugins/inputs/ipset/ipset_entries.go b/plugins/inputs/ipset/ipset_entries.go new file mode 100644 index 0000000000000..9ae2cbb701035 --- /dev/null +++ b/plugins/inputs/ipset/ipset_entries.go @@ -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 +} diff --git a/plugins/inputs/ipset/ipset_entries_test.go b/plugins/inputs/ipset/ipset_entries_test.go new file mode 100644 index 0000000000000..3f0348e71de6d --- /dev/null +++ b/plugins/inputs/ipset/ipset_entries_test.go @@ -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()) +} diff --git a/plugins/inputs/ipset/sample.conf b/plugins/inputs/ipset/sample.conf index a873eb79227f1..34c6cd92cf962 100644 --- a/plugins/inputs/ipset/sample.conf +++ b/plugins/inputs/ipset/sample.conf @@ -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" -