Skip to content
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
2cb9dd5
add nftables input plugin
ajohnson-aurora Sep 8, 2025
4e330fd
Add Sudo and Binary options
ansoni Sep 8, 2025
d8405dc
Remove unused configuration example
ansoni Sep 8, 2025
8a9e9da
Update Readme with linter feedback
ajohnson-aurora Sep 18, 2025
e4b33a2
go fmt updates
ajohnson-aurora Sep 18, 2025
9775fe8
PR Feedback
ajohnson-aurora Sep 23, 2025
5fc2700
missed long line
ajohnson-aurora Sep 23, 2025
edd1bd5
Add notlinux compliation and Address some README lint issues
ajohnson-aurora Sep 23, 2025
841b7e4
fix `make check` errors
ajohnson-aurora Sep 23, 2025
0921ca1
PR Feedback
ajohnson-aurora Oct 6, 2025
a55e09d
Add back speculative Telegraf Version
ajohnson-aurora Oct 6, 2025
0fef89a
More PR Feedback
ajohnson-aurora Oct 7, 2025
5651dfc
PR Feedback
ajohnson-aurora Oct 13, 2025
a46b125
Linter fixes
ajohnson-aurora Oct 13, 2025
34fd401
fix macos linter
ajohnson-aurora Oct 13, 2025
8dd3b49
PR Feedback
ajohnson-aurora Nov 4, 2025
56a509e
Use implicit declaration of error
srebhan Nov 5, 2025
a318d13
Improve comment
srebhan Nov 13, 2025
5f42e15
Cleanup init
srebhan Nov 13, 2025
ed20c94
Cleanup formatting in sample.conf
srebhan Nov 13, 2025
7fbca44
Cleanup variable naming and parameter order
srebhan Nov 13, 2025
f66f82e
Translate unit-test into testcase-based tests
srebhan Nov 13, 2025
15740e4
Remove debug output
srebhan Nov 13, 2025
41926d7
Merge functions
srebhan Nov 13, 2025
24ef6d9
Improve error reporting
srebhan Nov 13, 2025
4314b90
Add test-case for non-existant table
srebhan Nov 13, 2025
0da365f
Update plugins/inputs/nftables/README.md
skartikey Nov 18, 2025
e50e2a1
Update plugins/inputs/nftables/README.md
skartikey Nov 18, 2025
2095cbf
fix(inputs.nftables): Fix typo in test case directory name
skartikey Nov 19, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions plugins/inputs/all/nftables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
//go:build !custom || inputs || inputs.nftables

package all

import _ "github.com/influxdata/telegraf/plugins/inputs/nftables" // register plugin
86 changes: 86 additions & 0 deletions plugins/inputs/nftables/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# Nftables Plugin

The nftables plugin gathers packets and bytes counters for rules within
Linux's nftables firewall.
Comment thread
srebhan marked this conversation as resolved.
Outdated

Rules are identified through associated comment.
**Rules without comment are ignored**.

Before using this plugin **you must ensure that the rules you want to monitor
are named with a unique comment**. Comments are added using the 'comment
"my comment"' nftables options.
Comment thread
srebhan marked this conversation as resolved.
Outdated

The nftables command requires CAP_NET_ADMIN and CAP_NET_RAW capabilities.
You have several options to grant telegraf to run nftables:
Comment thread
srebhan marked this conversation as resolved.
Outdated

* Run telegraf as root. This is strongly discouraged.
* Configure systemd to run telegraf with CAP_NET_ADMIN and CAP_NET_RAW.
This is the simplest and recommended option.
* Configure sudo to grant telegraf to run nftables. This is the most restrictive
option, but requires sudo setup.
Comment thread
srebhan marked this conversation as resolved.
Outdated

⭐ Telegraf v1.37.0
🏷️ network, system
Comment thread
srebhan marked this conversation as resolved.
Outdated
💻 linux

## 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
```

Since telegraf will fork a process to run nftables, `AmbientCapabilities` is
required to transmit the capabilities bounding set to the forked process.

## Using sudo

You may edit your sudo configuration with the following:

```sudo
telegraf ALL=(root) NOPASSWD: /usr/bin/nft *
```
Comment thread
srebhan marked this conversation as resolved.
Outdated

## 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
Comment thread
srebhan marked this conversation as resolved.
Outdated

## Configuration

```toml
Comment thread
srebhan marked this conversation as resolved.
Outdated
# use sudo to run nftables
use_sudo = false
# defines the table to monitor:
tables = ["filter"]
```

## Tags

* All measurements have the following tags:
* table
* chain
* ruleid

The `ruleid` is the comment associated to the rule.

## Metrics

* nftables
* pkts (integer, count)
* bytes (integer, bytes)
Comment thread
srebhan marked this conversation as resolved.
Outdated

## Example Output

```bash
$ ./telegraf --config telegraf.conf --input-filter nftables --test
> nftables,chain=incoming,host=my_hostname,ruleid=comment_val_1,table=filter bytes=66435845i,pkts=133882i 1757367516000000000
> nftables,chain=outgoing,host=my_hostname,ruleid=comment_val2,table=filter bytes=25596512i,pkts=145129i 1757367516000000000
Comment thread
skartikey marked this conversation as resolved.
Outdated
```
Comment thread
srebhan marked this conversation as resolved.
Outdated
188 changes: 188 additions & 0 deletions plugins/inputs/nftables/nftables.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build linux

package nftables

import (
_ "embed"
"encoding/json"
"errors"
"fmt"
"os/exec"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

const measurement = "nftables"
Comment thread
srebhan marked this conversation as resolved.
Outdated

type Nftables struct {
UseSudo bool `toml:"use_sudo"`
Binary string `toml:"binary"`
Comment thread
srebhan marked this conversation as resolved.
Outdated
Tables []string
Comment thread
skartikey marked this conversation as resolved.
Outdated
}

//go:embed sample.conf
var sampleConfig string
Comment thread
srebhan marked this conversation as resolved.
Outdated

type Nftable struct {
Metainfo *Metainfo
Rules []*Rule
JSONSchemaVersion int `json:"json_schema_version"`
Comment thread
srebhan marked this conversation as resolved.
Outdated
}
Comment thread
srebhan marked this conversation as resolved.
Outdated

// UnmarshalJSON handles custom parsing of the nftable output which is
// designed in a generic that is not compatible with the generic parser.
Comment thread
srebhan marked this conversation as resolved.
Outdated
func (nftable *Nftable) UnmarshalJSON(b []byte) error {
var atable map[string][]map[string]json.RawMessage
Comment thread
skartikey marked this conversation as resolved.
Outdated
if err := json.Unmarshal(b, &atable); err != nil {
return fmt.Errorf("unable to unmarshal: %s", b)
}
// []map[string]interface
nfthings := atable["nftables"]
for _, nfthing := range nfthings {
hasKey := func(key string) bool { _, ok := nfthing[key]; return ok }
switch {
case hasKey("metainfo"):
var mi Metainfo
err := json.Unmarshal(nfthing["metainfo"], &mi)
if err != nil {
return fmt.Errorf("unable to parse metadata: %w", err)
}
nftable.Metainfo = &mi
case hasKey("rule"):
var r Rule
err := json.Unmarshal(nfthing["rule"], &r)
if err != nil {
return fmt.Errorf("unable to parse rule: %w", err)
}
nftable.Rules = append(nftable.Rules, &r)
default:
// something we are not parsing
}
}
return nil
}

type Metainfo struct {
Version string `json:"version"`
ReleaseName string `json:"release_name"`
JSONSchemaVersion int `json:"json_schema_version"`
}

type Rule struct {
Comment thread
skartikey marked this conversation as resolved.
Outdated
Family string
Table string
Chain string
Comment string
Counter *Counter
}

// UnmarshalJSON handles properly extracting the counter expression from
// the Exprs array input
func (rule *Rule) UnmarshalJSON(b []byte) error {
var raw struct {
Family string `json:"family"`
Table string `json:"table"`
Chain string `json:"chain"`
Comment string `json:"comment"`
Exprs []map[string]json.RawMessage `json:"expr"`
}
if err := json.Unmarshal(b, &raw); err != nil {
return fmt.Errorf("unable to unmarshal: %s", b)
}
rule.Family = raw.Family
rule.Table = raw.Table
rule.Chain = raw.Chain
rule.Comment = raw.Comment

// iterate rule expressions looking for the single counter
for _, expr := range raw.Exprs {
hasKey := func(key string) bool { _, ok := expr[key]; return ok }
if hasKey("counter") {
rule.Counter = &Counter{}
if err := json.Unmarshal(expr["counter"], rule.Counter); err != nil {
return fmt.Errorf("unable to parse counter: %w", err)
}
// we can return early since we are not looking for anything else
Comment thread
skartikey marked this conversation as resolved.
Outdated
return nil
}
}
return nil
}

type Counter struct {
Packets int64 `json:"packets"`
Bytes int64 `json:"bytes"`
}

func (*Nftables) SampleConfig() string {
return sampleConfig
}

func (*Nftables) Init() error {
return nil
}

func (nft *Nftables) Gather(acc telegraf.Accumulator) error {
if len(nft.Tables) == 0 {
return errors.New("invalid configuration - expected a 'Tables' entry with list of nftables to monitor")
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
for _, table := range nft.Tables {
err := nft.getTableData(table, acc)
if err != nil {
return err
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
}
return nil
}

// List a specific table and add to Accumulator
func (nft *Nftables) getTableData(tableName string, acc telegraf.Accumulator) error {
Comment thread
srebhan marked this conversation as resolved.
Outdated
Comment thread
srebhan marked this conversation as resolved.
Outdated
var binary string
if nft.Binary != "" {
binary = nft.Binary
} else {
binary = "nft"
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
nftablePath, err := exec.LookPath(binary)
if err != nil {
return errors.New("failed to find nft command ")
Comment thread
srebhan marked this conversation as resolved.
Outdated
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
var args []string
name := nftablePath
if nft.UseSudo {
name = "sudo"
args = append(args, nftablePath)
}
args = append(args, "--json", "list", "table", tableName)
Comment thread
srebhan marked this conversation as resolved.
Outdated
c := exec.Command(name, args...)
out, err := c.Output()
if err != nil {
return fmt.Errorf("error executing %s, error: %w", c, err)
Comment thread
skartikey marked this conversation as resolved.
Outdated
}
return parseNftableOutput(out, acc)
}

func parseNftableOutput(out []byte, acc telegraf.Accumulator) error {
Comment thread
srebhan marked this conversation as resolved.
Outdated
var nftable Nftable
err := json.Unmarshal(out, &nftable)
if err != nil {
return fmt.Errorf("error parsing: %s, Error: %w", out, err)
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
for _, rule := range nftable.Rules {
// Rule must have a Counter and a Comment
if rule.Counter != nil && len(rule.Comment) > 0 {
fields := map[string]interface{}{"bytes": rule.Counter.Bytes, "pkts": rule.Counter.Packets}
tags := map[string]string{"table": rule.Table, "chain": rule.Chain, "ruleid": rule.Comment}
acc.AddFields(measurement, fields, tags)
}
Comment thread
srebhan marked this conversation as resolved.
Outdated
}
return nil
}

func init() {
inputs.Add("nftables", func() telegraf.Input {
return &Nftables{}
})
}
33 changes: 33 additions & 0 deletions plugins/inputs/nftables/nftables_notlinux.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//go:generate ../../../tools/readme_config_includer/generator
//go:build !linux

package nftables

import (
_ "embed"

"github.com/influxdata/telegraf"
"github.com/influxdata/telegraf/plugins/inputs"
)

//go:embed sample.conf
var sampleConfig string

type Nftables struct {
Log telegraf.Logger `toml:"-"`
}

func (*Nftables) SampleConfig() string { return sampleConfig }

func (i *Nftables) Init() error {
i.Log.Warn("Current platform is not supported")
return nil
}

func (*Nftables) Gather(_ telegraf.Accumulator) error { return nil }

func init() {
inputs.Add("nftables", func() telegraf.Input {
return &Nftables{}
})
}
Loading
Loading