Add synproxy input plugin (#5683)
This commit is contained in:
parent
acfdc5576c
commit
23b6deee22
|
@ -143,6 +143,7 @@ import (
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
|
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/suricata"
|
_ "github.com/influxdata/telegraf/plugins/inputs/suricata"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/swap"
|
_ "github.com/influxdata/telegraf/plugins/inputs/swap"
|
||||||
|
_ "github.com/influxdata/telegraf/plugins/inputs/synproxy"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/syslog"
|
_ "github.com/influxdata/telegraf/plugins/inputs/syslog"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
|
_ "github.com/influxdata/telegraf/plugins/inputs/sysstat"
|
||||||
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
_ "github.com/influxdata/telegraf/plugins/inputs/system"
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Synproxy Input Plugin
|
||||||
|
|
||||||
|
The synproxy plugin gathers the synproxy counters. Synproxy is a Linux netfilter module used for SYN attack mitigation.
|
||||||
|
The use of synproxy is documented in `man iptables-extensions` under the SYNPROXY section.
|
||||||
|
|
||||||
|
|
||||||
|
### Configuration
|
||||||
|
|
||||||
|
The synproxy plugin does not need any configuration
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[inputs.synproxy]]
|
||||||
|
# no configuration
|
||||||
|
```
|
||||||
|
|
||||||
|
### Metrics
|
||||||
|
|
||||||
|
The following synproxy counters are gathered
|
||||||
|
|
||||||
|
- synproxy
|
||||||
|
- fields:
|
||||||
|
- cookie_invalid (uint32, packets, counter) - Invalid cookies
|
||||||
|
- cookie_retrans (uint32, packets, counter) - Cookies retransmitted
|
||||||
|
- cookie_valid (uint32, packets, counter) - Valid cookies
|
||||||
|
- entries (uint32, packets, counter) - Entries
|
||||||
|
- syn_received (uint32, packets, counter) - SYN received
|
||||||
|
- conn_reopened (uint32, packets, counter) - Connections reopened
|
||||||
|
|
||||||
|
### Sample Queries
|
||||||
|
|
||||||
|
Get the number of packets per 5 minutes for the measurement in the last hour from InfluxDB:
|
||||||
|
```
|
||||||
|
SELECT difference(last("cookie_invalid")) AS "cookie_invalid", difference(last("cookie_retrans")) AS "cookie_retrans", difference(last("cookie_valid")) AS "cookie_valid", difference(last("entries")) AS "entries", difference(last("syn_received")) AS "syn_received", difference(last("conn_reopened")) AS "conn_reopened" FROM synproxy WHERE time > NOW() - 1h GROUP BY time(5m) FILL(null);
|
||||||
|
```
|
||||||
|
|
||||||
|
### Troubleshooting
|
||||||
|
|
||||||
|
Execute the following CLI command in Linux to test the synproxy counters:
|
||||||
|
```
|
||||||
|
cat /proc/net/stat/synproxy
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example Output
|
||||||
|
|
||||||
|
This section shows example output in Line Protocol format.
|
||||||
|
|
||||||
|
```
|
||||||
|
synproxy,host=Filter-GW01,rack=filter-node1 conn_reopened=0i,cookie_invalid=235i,cookie_retrans=0i,cookie_valid=8814i,entries=0i,syn_received=8742i 1549550634000000000
|
||||||
|
```
|
|
@ -0,0 +1,121 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package synproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Synproxy struct {
|
||||||
|
// Synproxy stats filename (proc filesystem)
|
||||||
|
statFile string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) Description() string {
|
||||||
|
return "Get synproxy counter statistics from procfs"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) SampleConfig() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) Gather(acc telegraf.Accumulator) error {
|
||||||
|
data, err := k.getSynproxyStat()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
acc.AddCounter("synproxy", data, map[string]string{})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func inSlice(haystack []string, needle string) bool {
|
||||||
|
for _, val := range haystack {
|
||||||
|
if needle == val {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) getSynproxyStat() (map[string]interface{}, error) {
|
||||||
|
var hname []string
|
||||||
|
counters := []string{"entries", "syn_received", "cookie_invalid", "cookie_valid", "cookie_retrans", "conn_reopened"}
|
||||||
|
fields := make(map[string]interface{})
|
||||||
|
|
||||||
|
// Open synproxy file in proc filesystem
|
||||||
|
file, err := os.Open(k.statFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
// Initialise expected fields
|
||||||
|
for _, val := range counters {
|
||||||
|
fields[val] = uint32(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
// Read header row
|
||||||
|
if scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// Parse fields separated by whitespace
|
||||||
|
dataFields := strings.Fields(line)
|
||||||
|
for _, val := range dataFields {
|
||||||
|
if !inSlice(counters, val) {
|
||||||
|
val = ""
|
||||||
|
}
|
||||||
|
hname = append(hname, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(hname) == 0 {
|
||||||
|
return nil, fmt.Errorf("invalid data")
|
||||||
|
}
|
||||||
|
// Read data rows
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
// Parse fields separated by whitespace
|
||||||
|
dataFields := strings.Fields(line)
|
||||||
|
// If number of data fields do not match number of header fields
|
||||||
|
if len(dataFields) != len(hname) {
|
||||||
|
return nil, fmt.Errorf("invalid number of columns in data, expected %d found %d", len(hname),
|
||||||
|
len(dataFields))
|
||||||
|
}
|
||||||
|
for i, val := range dataFields {
|
||||||
|
// Convert from hexstring to int32
|
||||||
|
x, err := strconv.ParseUint(val, 16, 32)
|
||||||
|
// If field is not a valid hexstring
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("invalid value '%s' found", val)
|
||||||
|
}
|
||||||
|
if hname[i] != "" {
|
||||||
|
fields[hname[i]] = fields[hname[i]].(uint32) + uint32(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getHostProc() string {
|
||||||
|
procPath := "/proc"
|
||||||
|
if os.Getenv("HOST_PROC") != "" {
|
||||||
|
procPath = os.Getenv("HOST_PROC")
|
||||||
|
}
|
||||||
|
return procPath
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("synproxy", func() telegraf.Input {
|
||||||
|
return &Synproxy{
|
||||||
|
statFile: path.Join(getHostProc(), "/net/stat/synproxy"),
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
// +build !linux
|
||||||
|
|
||||||
|
package synproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf"
|
||||||
|
"github.com/influxdata/telegraf/plugins/inputs"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Synproxy struct{}
|
||||||
|
|
||||||
|
func (k *Synproxy) Gather(acc telegraf.Accumulator) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) Description() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (k *Synproxy) SampleConfig() string {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
inputs.Add("synproxy", func() telegraf.Input {
|
||||||
|
log.Print("W! [inputs.synproxy] Current platform is not supported")
|
||||||
|
return &Synproxy{}
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,169 @@
|
||||||
|
// +build linux
|
||||||
|
|
||||||
|
package synproxy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/influxdata/telegraf/testutil"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestSynproxyFileNormal(t *testing.T) {
|
||||||
|
testSynproxyFileData(t, synproxyFileNormal, synproxyResultNormal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSynproxyFileOverflow(t *testing.T) {
|
||||||
|
testSynproxyFileData(t, synproxyFileOverflow, synproxyResultOverflow)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSynproxyFileExtended(t *testing.T) {
|
||||||
|
testSynproxyFileData(t, synproxyFileExtended, synproxyResultNormal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSynproxyFileAltered(t *testing.T) {
|
||||||
|
testSynproxyFileData(t, synproxyFileAltered, synproxyResultNormal)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSynproxyFileHeaderMismatch(t *testing.T) {
|
||||||
|
tmpfile := makeFakeSynproxyFile([]byte(synproxyFileHeaderMismatch))
|
||||||
|
defer os.Remove(tmpfile)
|
||||||
|
|
||||||
|
k := Synproxy{
|
||||||
|
statFile: tmpfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
err := k.Gather(&acc)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid number of columns in data")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSynproxyFileInvalidHex(t *testing.T) {
|
||||||
|
tmpfile := makeFakeSynproxyFile([]byte(synproxyFileInvalidHex))
|
||||||
|
defer os.Remove(tmpfile)
|
||||||
|
|
||||||
|
k := Synproxy{
|
||||||
|
statFile: tmpfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
err := k.Gather(&acc)
|
||||||
|
assert.Error(t, err)
|
||||||
|
assert.Contains(t, err.Error(), "invalid value")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNoSynproxyFile(t *testing.T) {
|
||||||
|
tmpfile := makeFakeSynproxyFile([]byte(synproxyFileNormal))
|
||||||
|
// Remove file to generate "no such file" error
|
||||||
|
os.Remove(tmpfile)
|
||||||
|
|
||||||
|
k := Synproxy{
|
||||||
|
statFile: tmpfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
err := k.Gather(&acc)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Valid Synproxy file
|
||||||
|
const synproxyFileNormal = `entries syn_received cookie_invalid cookie_valid cookie_retrans conn_reopened
|
||||||
|
00000000 00007a88 00002af7 00007995 00000000 00000000
|
||||||
|
00000000 0000892c 000015e3 00008852 00000000 00000000
|
||||||
|
00000000 00007a80 00002ccc 0000796a 00000000 00000000
|
||||||
|
00000000 000079f7 00002bf5 0000790a 00000000 00000000
|
||||||
|
00000000 00007a08 00002c9a 00007901 00000000 00000000
|
||||||
|
00000000 00007cfc 00002b36 000078fd 00000000 00000000
|
||||||
|
00000000 000079c2 00002c2b 000078d6 00000000 00000000
|
||||||
|
00000000 0000798a 00002ba8 000078a0 00000000 00000000`
|
||||||
|
|
||||||
|
const synproxyFileOverflow = `entries syn_received cookie_invalid cookie_valid cookie_retrans conn_reopened
|
||||||
|
00000000 80000001 e0000000 80000001 00000000 00000000
|
||||||
|
00000000 80000003 f0000009 80000003 00000000 00000000`
|
||||||
|
|
||||||
|
const synproxyFileHeaderMismatch = `entries syn_received cookie_invalid cookie_valid cookie_retrans
|
||||||
|
00000000 00000002 00000000 00000002 00000000 00000000
|
||||||
|
00000000 00000004 00000015 00000004 00000000 00000000
|
||||||
|
00000000 00000003 00000000 00000003 00000000 00000000
|
||||||
|
00000000 00000002 00000000 00000002 00000000 00000000
|
||||||
|
00000000 00000003 00000009 00000003 00000000 00000000
|
||||||
|
00000000 00000003 00000009 00000003 00000000 00000000
|
||||||
|
00000000 00000001 00000000 00000001 00000000 00000000
|
||||||
|
00000000 00000003 00000009 00000003 00000000 00000000`
|
||||||
|
|
||||||
|
const synproxyFileInvalidHex = `entries syn_received cookie_invalid cookie_valid cookie_retrans conn_reopened
|
||||||
|
entries 00000002 00000000 00000002 00000000 00000000
|
||||||
|
00000000 00000003 00000009 00000003 00000000 00000000`
|
||||||
|
|
||||||
|
const synproxyFileExtended = `entries syn_received cookie_invalid cookie_valid cookie_retrans conn_reopened new_counter
|
||||||
|
00000000 00007a88 00002af7 00007995 00000000 00000000 00000000
|
||||||
|
00000000 0000892c 000015e3 00008852 00000000 00000000 00000000
|
||||||
|
00000000 00007a80 00002ccc 0000796a 00000000 00000000 00000000
|
||||||
|
00000000 000079f7 00002bf5 0000790a 00000000 00000000 00000000
|
||||||
|
00000000 00007a08 00002c9a 00007901 00000000 00000000 00000000
|
||||||
|
00000000 00007cfc 00002b36 000078fd 00000000 00000000 00000000
|
||||||
|
00000000 000079c2 00002c2b 000078d6 00000000 00000000 00000000
|
||||||
|
00000000 0000798a 00002ba8 000078a0 00000000 00000000 00000000`
|
||||||
|
|
||||||
|
const synproxyFileAltered = `entries cookie_invalid cookie_valid syn_received conn_reopened
|
||||||
|
00000000 00002af7 00007995 00007a88 00000000
|
||||||
|
00000000 000015e3 00008852 0000892c 00000000
|
||||||
|
00000000 00002ccc 0000796a 00007a80 00000000
|
||||||
|
00000000 00002bf5 0000790a 000079f7 00000000
|
||||||
|
00000000 00002c9a 00007901 00007a08 00000000
|
||||||
|
00000000 00002b36 000078fd 00007cfc 00000000
|
||||||
|
00000000 00002c2b 000078d6 000079c2 00000000
|
||||||
|
00000000 00002ba8 000078a0 0000798a 00000000`
|
||||||
|
|
||||||
|
var synproxyResultNormal = map[string]interface{}{
|
||||||
|
"entries": uint32(0x00000000),
|
||||||
|
"syn_received": uint32(0x0003e27b),
|
||||||
|
"cookie_invalid": uint32(0x0001493e),
|
||||||
|
"cookie_valid": uint32(0x0003d7cf),
|
||||||
|
"cookie_retrans": uint32(0x00000000),
|
||||||
|
"conn_reopened": uint32(0x00000000),
|
||||||
|
}
|
||||||
|
|
||||||
|
var synproxyResultOverflow = map[string]interface{}{
|
||||||
|
"entries": uint32(0x00000000),
|
||||||
|
"syn_received": uint32(0x00000004),
|
||||||
|
"cookie_invalid": uint32(0xd0000009),
|
||||||
|
"cookie_valid": uint32(0x00000004),
|
||||||
|
"cookie_retrans": uint32(0x00000000),
|
||||||
|
"conn_reopened": uint32(0x00000000),
|
||||||
|
}
|
||||||
|
|
||||||
|
func testSynproxyFileData(t *testing.T, fileData string, telegrafData map[string]interface{}) {
|
||||||
|
tmpfile := makeFakeSynproxyFile([]byte(fileData))
|
||||||
|
defer os.Remove(tmpfile)
|
||||||
|
|
||||||
|
k := Synproxy{
|
||||||
|
statFile: tmpfile,
|
||||||
|
}
|
||||||
|
|
||||||
|
acc := testutil.Accumulator{}
|
||||||
|
err := k.Gather(&acc)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
acc.AssertContainsFields(t, "synproxy", telegrafData)
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeFakeSynproxyFile(content []byte) string {
|
||||||
|
tmpfile, err := ioutil.TempFile("", "synproxy_test")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := tmpfile.Write(content); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
if err := tmpfile.Close(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return tmpfile.Name()
|
||||||
|
}
|
Loading…
Reference in New Issue