Skip to content

Commit

Permalink
feat(inputs.ipset): Add metric for number of entries and individual I…
Browse files Browse the repository at this point in the history
…Ps (#16124)
  • Loading branch information
verybadsoldier authored Dec 27, 2024
1 parent b92700f commit 0c7c424
Show file tree
Hide file tree
Showing 5 changed files with 197 additions and 3 deletions.
3 changes: 2 additions & 1 deletion plugins/inputs/ipset/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 11 additions & 1 deletion plugins/inputs/ipset/ipset.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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") &&
Expand Down Expand Up @@ -101,6 +108,9 @@ func (i *Ipset) Gather(acc telegraf.Accumulator) error {
acc.AddCounter(measurement, fields, tags)
}
}

i.entriesParser.commit(acc)

return nil
}

Expand Down
86 changes: 86 additions & 0 deletions plugins/inputs/ipset/ipset_entries.go
Original file line number Diff line number Diff line change
@@ -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
}
96 changes: 96 additions & 0 deletions plugins/inputs/ipset/ipset_entries_test.go
Original file line number Diff line number Diff line change
@@ -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())
}
3 changes: 2 additions & 1 deletion plugins/inputs/ipset/sample.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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"

0 comments on commit 0c7c424

Please sign in to comment.