Add synproxy input plugin (#5683)

This commit is contained in:
Remi Frenay 2019-11-21 20:26:59 +01:00 committed by Daniel Nelson
parent acfdc5576c
commit 23b6deee22
5 changed files with 371 additions and 0 deletions

View File

@ -143,6 +143,7 @@ import (
_ "github.com/influxdata/telegraf/plugins/inputs/statsd"
_ "github.com/influxdata/telegraf/plugins/inputs/suricata"
_ "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/sysstat"
_ "github.com/influxdata/telegraf/plugins/inputs/system"

View File

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

View File

@ -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"),
}
})
}

View File

@ -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{}
})
}

View File

@ -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()
}