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/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"
|
||||
|
|
|
@ -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