1
0
Fork 0

Adding upstream version 1.34.4.

Signed-off-by: Daniel Baumann <daniel@debian.org>
This commit is contained in:
Daniel Baumann 2025-05-24 07:26:29 +02:00
parent e393c3af3f
commit 4978089aab
Signed by: daniel
GPG key ID: FBB4F0E80A80222F
4963 changed files with 677545 additions and 0 deletions

View file

@ -0,0 +1,104 @@
# Ipset Input Plugin
This plugin gathers packets and bytes counters from [Linux IP sets][ipsets]
using the `ipset` command line tool.
> [!NOTE]
> IP sets created without the "counters" option are ignored.
⭐ Telegraf v1.6.0
🏷️ network, system
💻 linux
[ipsets]: https://ipset.netfilter.org/
## Global configuration options <!-- @/docs/includes/plugin_config.md -->
In addition to the plugin-specific configuration settings, plugins support
additional global and plugin configuration settings. These settings are used to
modify metrics, tags, and field or create aliases and configure ordering, etc.
See the [CONFIGURATION.md][CONFIGURATION.md] for more details.
[CONFIGURATION.md]: ../../../docs/CONFIGURATION.md#plugins
## Configuration
```toml @sample.conf
# Gather packets and bytes counters from Linux ipsets
[[inputs.ipset]]
## By default, we only show sets which have already matched at least 1 packet.
## set include_unmatched_sets = true to gather them all.
# include_unmatched_sets = false
## Adjust your sudo settings appropriately if using this option ("sudo ipset save")
## 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"
```
### Permissions
There are 3 ways to grant telegraf the right to run ipset:
- Run as root (strongly discouraged)
- Use sudo
- Configure systemd to run telegraf with CAP_NET_ADMIN and CAP_NET_RAW
capabilities
#### Using sudo
To use sudo set the `use_sudo` option to `true` and update your sudoers file:
```bash
$ visudo
# Add the following line:
Cmnd_Alias IPSETSAVE = /sbin/ipset save
telegraf ALL=(root) NOPASSWD: IPSETSAVE
Defaults!IPSETSAVE !logfile, !syslog, !pam_session
```
#### Using systemd capabilities
You may run `systemctl edit telegraf.service` and add the following:
```text
[Service]
CapabilityBoundingSet=CAP_NET_RAW CAP_NET_ADMIN
AmbientCapabilities=CAP_NET_RAW CAP_NET_ADMIN
```
## Metrics
- ipset
- tags:
- rule
- set
- fields:
- timeout
- packets
- bytes
- ipset (for `count_per_ip_entries = true`)
- tags:
- set
- fields:
- entries
- ips
## Example Output
```sh
$ sudo ipset save
create myset hash:net family inet hashsize 1024 maxelem 65536 counters comment
add myset 10.69.152.1 packets 8 bytes 672 comment "machine A"
```
```text
ipset,rule=10.69.152.1,host=trashme,set=myset bytes_total=8i,packets_total=672i 1507615028000000000
```

View file

@ -0,0 +1,149 @@
//go:generate ../../../tools/readme_config_includer/generator
package ipset
import (
"bufio"
"bytes"
_ "embed"
"fmt"
"os/exec"
"strconv"
"strings"
"time"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/internal"
"github.com/influxdata/telegraf/plugins/inputs"
)
//go:embed sample.conf
var sampleConfig string
var defaultTimeout = config.Duration(time.Second)
const measurement = "ipset"
type Ipset struct {
IncludeUnmatchedSets bool `toml:"include_unmatched_sets"`
UseSudo bool `toml:"use_sudo"`
Timeout config.Duration `toml:"timeout"`
CountPerIPEntries bool
lister setLister
entriesParser ipsetEntries
}
type setLister func(Timeout config.Duration, UseSudo bool) (*bytes.Buffer, error)
func (*Ipset) SampleConfig() string {
return sampleConfig
}
func (*Ipset) Init() error {
_, err := exec.LookPath("ipset")
if err != nil {
return err
}
return nil
}
func (i *Ipset) Gather(acc telegraf.Accumulator) error {
out, e := i.lister(i.Timeout, i.UseSudo)
if e != nil {
acc.AddError(e)
}
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") || !strings.Contains(nocomment, "bytes") {
continue
}
data := strings.Fields(line)
if len(data) < 7 {
acc.AddError(fmt.Errorf("error parsing line (expected at least 7 fields): %s", line))
continue
}
if data[0] == "add" && (data[4] != "0" || i.IncludeUnmatchedSets) {
tags := map[string]string{
"set": data[1],
"rule": data[2],
}
fields := make(map[string]interface{}, 3)
for i, field := range data {
switch field {
case "timeout":
val, err := strconv.ParseUint(data[i+1], 10, 64)
if err != nil {
acc.AddError(err)
}
fields["timeout"] = val
case "packets":
val, err := strconv.ParseUint(data[i+1], 10, 64)
if err != nil {
acc.AddError(err)
}
fields["packets_total"] = val
case "bytes":
val, err := strconv.ParseUint(data[i+1], 10, 64)
if err != nil {
acc.AddError(err)
}
fields["bytes_total"] = val
}
}
acc.AddCounter(measurement, fields, tags)
}
}
i.entriesParser.commit(acc)
return nil
}
func setList(timeout config.Duration, useSudo bool) (*bytes.Buffer, error) {
// Is ipset installed ?
ipsetPath, err := exec.LookPath("ipset")
if err != nil {
return nil, err
}
var args []string
cmdName := ipsetPath
if useSudo {
cmdName = "sudo"
args = append(args, ipsetPath)
}
args = append(args, "save")
cmd := exec.Command(cmdName, args...)
var out bytes.Buffer
cmd.Stdout = &out
err = internal.RunTimeout(cmd, time.Duration(timeout))
if err != nil {
return &out, fmt.Errorf("error running ipset save: %w", err)
}
return &out, nil
}
func init() {
inputs.Add("ipset", func() telegraf.Input {
return &Ipset{
lister: setList,
Timeout: defaultTimeout,
}
})
}

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,97 @@
package ipset
import (
"testing"
"time"
"github.com/stretchr/testify/require"
"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/testutil"
)
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

@ -0,0 +1,150 @@
package ipset
import (
"bytes"
"errors"
"reflect"
"testing"
"github.com/influxdata/telegraf/config"
"github.com/influxdata/telegraf/testutil"
)
func TestIpset(t *testing.T) {
tests := []struct {
name string
value string
tags []map[string]string
fields [][]map[string]interface{}
err error
}{
{
name: "0 sets, no results",
value: "",
},
{
name: "Empty sets, no values",
value: `create myset hash:net family inet hashsize 1024 maxelem 65536
create myset2 hash:net,port family inet hashsize 16384 maxelem 524288 counters comment
`,
},
{
name: "Non-empty sets, but no counters, no results",
value: `create myset hash:net family inet hashsize 1024 maxelem 65536
add myset 1.2.3.4
`,
},
{
name: "Line with data but not enough fields",
value: `create hash:net family inet hashsize 1024 maxelem 65536 counters
add myset 4.5.6.7 packets 123 bytes
`,
err: errors.New("error parsing line (expected at least 7 fields): \t\t\t\tadd myset 4.5.6.7 packets 123 bytes"),
},
{
name: "Non-empty sets, counters, no comment",
value: `create myset hash:net family inet hashsize 1024 maxelem 65536 counters
add myset 1.2.3.4 packets 1328 bytes 79680
add myset 2.3.4.5 packets 0 bytes 0
add myset 3.4.5.6 packets 3 bytes 222
`,
tags: []map[string]string{
{"set": "myset", "rule": "1.2.3.4"},
{"set": "myset", "rule": "3.4.5.6"},
},
fields: [][]map[string]interface{}{
{map[string]interface{}{"packets_total": uint64(1328), "bytes_total": uint64(79680)}},
{map[string]interface{}{"packets_total": uint64(3), "bytes_total": uint64(222)}},
},
},
{
name: "Sets with counters and comment",
value: `create myset hash:net family inet hashsize 1024 maxelem 65536 counters comment
add myset 1.2.3.4 packets 1328 bytes 79680 comment "first IP"
add myset 2.3.4.5 packets 0 bytes 0 comment "2nd IP"
add myset 3.4.5.6 packets 3 bytes 222 "3rd IP"
`,
tags: []map[string]string{
{"set": "myset", "rule": "1.2.3.4"},
{"set": "myset", "rule": "3.4.5.6"},
},
fields: [][]map[string]interface{}{
{map[string]interface{}{"packets_total": uint64(1328), "bytes_total": uint64(79680)}},
{map[string]interface{}{"packets_total": uint64(3), "bytes_total": uint64(222)}},
},
},
{
name: "Sets with and without timeouts",
value: `create counter-test hash:ip family inet hashsize 1024 maxelem 65536 timeout 1800 counters
add counter-test 192.168.1.1 timeout 1792 packets 8 bytes 672
create counter-test2 hash:ip family inet hashsize 1024 maxelem 65536 counters
add counter-test2 192.168.1.1 packets 18 bytes 673
`,
tags: []map[string]string{
{"set": "counter-test", "rule": "192.168.1.1"},
{"set": "counter-test2", "rule": "192.168.1.1"},
},
fields: [][]map[string]interface{}{
{map[string]interface{}{"packets_total": uint64(8), "bytes_total": uint64(672), "timeout": uint64(1792)}},
{map[string]interface{}{"packets_total": uint64(18), "bytes_total": uint64(673)}},
},
},
}
for i, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
i++
ips := &Ipset{
lister: func(config.Duration, bool) (*bytes.Buffer, error) {
return bytes.NewBufferString(tt.value), nil
},
}
acc := new(testutil.Accumulator)
err := acc.GatherError(ips.Gather)
if !reflect.DeepEqual(tt.err, err) {
t.Errorf("%d: expected error '%#v' got '%#v'", i, tt.err, err)
}
if len(tt.tags) == 0 {
n := acc.NFields()
if n != 0 {
t.Errorf("%d: expected 0 values got %d", i, n)
}
return
}
n := 0
for j, tags := range tt.tags {
for k, fields := range tt.fields[j] {
if len(acc.Metrics) < n+1 {
t.Errorf("%d: expected at least %d values got %d", i, n+1, len(acc.Metrics))
break
}
m := acc.Metrics[n]
if !reflect.DeepEqual(m.Measurement, measurement) {
t.Errorf("%d %d %d: expected measurement '%#v' got '%#v'\n", i, j, k, measurement, m.Measurement)
}
if !reflect.DeepEqual(m.Tags, tags) {
t.Errorf("%d %d %d: expected tags\n%#v got\n%#v\n", i, j, k, tags, m.Tags)
}
if !reflect.DeepEqual(m.Fields, fields) {
t.Errorf("%d %d %d: expected fields\n%#v got\n%#v\n", i, j, k, fields, m.Fields)
}
n++
}
}
})
}
}
func TestIpset_Gather_listerError(t *testing.T) {
errFoo := errors.New("error foobar")
ips := &Ipset{
lister: func(config.Duration, bool) (*bytes.Buffer, error) {
return new(bytes.Buffer), errFoo
},
}
acc := new(testutil.Accumulator)
err := acc.GatherError(ips.Gather)
if !reflect.DeepEqual(err, errFoo) {
t.Errorf("Expected error %#v got\n%#v\n", errFoo, err)
}
}

View file

@ -0,0 +1,16 @@
# Gather packets and bytes counters from Linux ipsets
[[inputs.ipset]]
## By default, we only show sets which have already matched at least 1 packet.
## set include_unmatched_sets = true to gather them all.
# include_unmatched_sets = false
## Adjust your sudo settings appropriately if using this option ("sudo ipset save")
## 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"