Merge pull request #685 from cmattoon/conntrack-stat

Adds ConntrackStats (/proc/net/stat/nf_conntrack)
This commit is contained in:
shirou 2019-06-01 11:50:09 +09:00 committed by GitHub
commit 5335e3fd50
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 360 additions and 0 deletions

View File

@ -213,6 +213,12 @@ func ReadInts(filename string) ([]int64, error) {
return ret, nil
}
// Parse Hex to uint32 without error
func HexToUint32(hex string) uint32 {
vv, _ := strconv.ParseUint(hex, 16, 32)
return uint32(vv)
}
// Parse to int32 without error
func mustParseInt32(val string) int32 {
vv, _ := strconv.ParseInt(val, 10, 32)

View File

@ -51,6 +51,12 @@ func TestByteToString(t *testing.T) {
}
}
func TestHexToUint32(t *testing.T) {
if HexToUint32("FFFFFFFF") != 4294967295 {
t.Error("Could not convert")
}
}
func TestmustParseInt32(t *testing.T) {
ret := mustParseInt32("11111")
if ret != int32(11111) {

View File

@ -70,6 +70,96 @@ type FilterStat struct {
ConnTrackMax int64 `json:"conntrackMax"`
}
// ConntrackStat has conntrack summary info
type ConntrackStat struct {
Entries uint32 `json:"entries"` // Number of entries in the conntrack table
Searched uint32 `json:"searched"` // Number of conntrack table lookups performed
Found uint32 `json:"found"` // Number of searched entries which were successful
New uint32 `json:"new"` // Number of entries added which were not expected before
Invalid uint32 `json:"invalid"` // Number of packets seen which can not be tracked
Ignore uint32 `json:"ignore"` // Packets seen which are already connected to an entry
Delete uint32 `json:"delete"` // Number of entries which were removed
DeleteList uint32 `json:"delete_list"` // Number of entries which were put to dying list
Insert uint32 `json:"insert"` // Number of entries inserted into the list
InsertFailed uint32 `json:"insert_failed"` // # insertion attempted but failed (same entry exists)
Drop uint32 `json:"drop"` // Number of packets dropped due to conntrack failure.
EarlyDrop uint32 `json:"early_drop"` // Dropped entries to make room for new ones, if maxsize reached
IcmpError uint32 `json:"icmp_error"` // Subset of invalid. Packets that can't be tracked d/t error
ExpectNew uint32 `json:"expect_new"` // Entries added after an expectation was already present
ExpectCreate uint32 `json:"expect_create"` // Expectations added
ExpectDelete uint32 `json:"expect_delete"` // Expectations deleted
SearchRestart uint32 `json:"search_restart"` // Conntrack table lookups restarted due to hashtable resizes
}
func NewConntrackStat(e uint32, s uint32, f uint32, n uint32, inv uint32, ign uint32, del uint32, dlst uint32, ins uint32, insfail uint32, drop uint32, edrop uint32, ie uint32, en uint32, ec uint32, ed uint32, sr uint32) *ConntrackStat {
return &ConntrackStat{
Entries: e,
Searched: s,
Found: f,
New: n,
Invalid: inv,
Ignore: ign,
Delete: del,
DeleteList: dlst,
Insert: ins,
InsertFailed: insfail,
Drop: drop,
EarlyDrop: edrop,
IcmpError: ie,
ExpectNew: en,
ExpectCreate: ec,
ExpectDelete: ed,
SearchRestart: sr,
}
}
type ConntrackStatList struct {
items []*ConntrackStat
}
func NewConntrackStatList() *ConntrackStatList {
return &ConntrackStatList{
items: []*ConntrackStat{},
}
}
func (l *ConntrackStatList) Append(c *ConntrackStat) {
l.items = append(l.items, c)
}
func (l *ConntrackStatList) Items() []ConntrackStat {
items := make([]ConntrackStat, len(l.items), len(l.items))
for i, el := range l.items {
items[i] = *el
}
return items
}
// Summary returns a single-element list with totals from all list items.
func (l *ConntrackStatList) Summary() []ConntrackStat {
summary := NewConntrackStat(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)
for _, cs := range l.items {
summary.Entries += cs.Entries
summary.Searched += cs.Searched
summary.Found += cs.Found
summary.New += cs.New
summary.Invalid += cs.Invalid
summary.Ignore += cs.Ignore
summary.Delete += cs.Delete
summary.DeleteList += cs.DeleteList
summary.Insert += cs.Insert
summary.InsertFailed += cs.InsertFailed
summary.Drop += cs.Drop
summary.EarlyDrop += cs.EarlyDrop
summary.IcmpError += cs.IcmpError
summary.ExpectNew += cs.ExpectNew
summary.ExpectCreate += cs.ExpectCreate
summary.ExpectDelete += cs.ExpectDelete
summary.SearchRestart += cs.SearchRestart
}
return []ConntrackStat{*summary}
}
var constMap = map[string]int{
"unix": syscall.AF_UNIX,
"TCP": syscall.SOCK_STREAM,
@ -108,6 +198,11 @@ func (n InterfaceAddr) String() string {
return string(s)
}
func (n ConntrackStat) String() string {
s, _ := json.Marshal(n)
return string(s)
}
func Interfaces() ([]InterfaceStat, error) {
return InterfacesWithContext(context.Background())
}

View File

@ -6,6 +6,7 @@ import (
"context"
"errors"
"fmt"
"github.com/shirou/gopsutil/internal/common"
"os/exec"
"regexp"
"strconv"
@ -271,6 +272,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for darwin")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.

View File

@ -24,6 +24,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return []FilterStat{}, common.ErrNotImplementedError
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
func ProtoCounters(protocols []string) ([]ProtoCountersStat, error) {
return ProtoCountersWithContext(context.Background(), protocols)
}

View File

@ -112,6 +112,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for freebsd")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, errors.New("ConntrackStats not implemented for freebsd")
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.

View File

@ -18,6 +18,26 @@ import (
"github.com/shirou/gopsutil/internal/common"
)
const ( // Conntrack Column numbers
CT_ENTRIES = iota
CT_SEARCHED
CT_FOUND
CT_NEW
CT_INVALID
CT_IGNORE
CT_DELETE
CT_DELETE_LIST
CT_INSERT
CT_INSERT_FAILED
CT_DROP
CT_EARLY_DROP
CT_ICMP_ERROR
CT_EXPECT_NEW
CT_EXPECT_CREATE
CT_EXPECT_DELETE
CT_SEARCH_RESTART
)
// NetIOCounters returnes network I/O statistics for every network
// interface installed on the system. If pernic argument is false,
// return only sum of all information (which name is 'all'). If true,
@ -232,6 +252,58 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return stats, nil
}
// ConntrackStats returns more detailed info about the conntrack table
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
// ConntrackStatsWithContext returns more detailed info about the conntrack table
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return conntrackStatsFromFile(common.HostProc("net/stat/nf_conntrack"), percpu)
}
// conntrackStatsFromFile returns more detailed info about the conntrack table
// from `filename`
// If 'percpu' is false, the result will contain exactly one item with totals/summary
func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, error) {
lines, err := common.ReadLines(filename)
if err != nil {
return nil, err
}
statlist := NewConntrackStatList()
for _, line := range lines {
fields := strings.Fields(line)
if len(fields) == 17 && fields[0] != "entries" {
statlist.Append(NewConntrackStat(
common.HexToUint32(fields[CT_ENTRIES]),
common.HexToUint32(fields[CT_SEARCHED]),
common.HexToUint32(fields[CT_FOUND]),
common.HexToUint32(fields[CT_NEW]),
common.HexToUint32(fields[CT_INVALID]),
common.HexToUint32(fields[CT_IGNORE]),
common.HexToUint32(fields[CT_DELETE]),
common.HexToUint32(fields[CT_DELETE_LIST]),
common.HexToUint32(fields[CT_INSERT]),
common.HexToUint32(fields[CT_INSERT_FAILED]),
common.HexToUint32(fields[CT_DROP]),
common.HexToUint32(fields[CT_EARLY_DROP]),
common.HexToUint32(fields[CT_ICMP_ERROR]),
common.HexToUint32(fields[CT_EXPECT_NEW]),
common.HexToUint32(fields[CT_EXPECT_CREATE]),
common.HexToUint32(fields[CT_EXPECT_DELETE]),
common.HexToUint32(fields[CT_SEARCH_RESTART]),
))
}
}
if percpu {
return statlist.Items(), nil
}
return statlist.Summary(), nil
}
// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
var TCPStatuses = map[string]string{
"01": "ESTABLISHED",

View File

@ -161,3 +161,142 @@ func TestReverse(t *testing.T) {
src := []byte{0x01, 0x02, 0x03}
assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src))
}
func TestConntrackStatFileParsing(t *testing.T) {
tmpfile, err := ioutil.TempFile("", "proc_net_stat_conntrack")
defer os.Remove(tmpfile.Name())
assert.Nil(t, err, "Temporary file creation failed: ", err)
data := []byte(`
entries searched found new invalid ignore delete delete_list insert insert_failed drop early_drop icmp_error expect_new expect_create expect_delete search_restart
0000007b 00000000 00000000 00000000 000b115a 00000084 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000004a
0000007b 00000000 00000000 00000000 0007eee5 00000068 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000035
0000007b 00000000 00000000 00000000 0090346b 00000057 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000025
0000007b 00000000 00000000 00000000 0005920f 00000069 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000064
0000007b 00000000 00000000 00000000 000331ff 00000059 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000003b
0000007b 00000000 00000000 00000000 000314ea 00000066 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000054
0000007b 00000000 00000000 00000000 0002b270 00000055 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 0000003d
0000007b 00000000 00000000 00000000 0002f67d 00000057 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000042
`)
// Expected results
slist := NewConntrackStatList()
slist.Append(&ConntrackStat{
Entries: 123,
Searched: 0,
Found: 0,
New: 0,
Invalid: 725338,
Ignore: 132,
Delete: 0,
DeleteList: 0,
Insert: 0,
InsertFailed: 0,
Drop: 0,
EarlyDrop: 0,
IcmpError: 0,
ExpectNew: 0,
ExpectCreate: 0,
ExpectDelete: 0,
SearchRestart: 74,
})
slist.Append(&ConntrackStat{123, 0, 0, 0, 519909, 104, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 53})
slist.Append(&ConntrackStat{123, 0, 0, 0, 9450603, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 37})
slist.Append(&ConntrackStat{123, 0, 0, 0, 365071, 105, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 100})
slist.Append(&ConntrackStat{123, 0, 0, 0, 209407, 89, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 59})
slist.Append(&ConntrackStat{123, 0, 0, 0, 201962, 102, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 84})
slist.Append(&ConntrackStat{123, 0, 0, 0, 176752, 85, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 61})
slist.Append(&ConntrackStat{123, 0, 0, 0, 194173, 87, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 66})
// Write data to tempfile
_, err = tmpfile.Write(data)
assert.Nil(t, err, "Temporary file writing failed: ", err)
// Function under test
stats, err := conntrackStatsFromFile(tmpfile.Name(), true)
assert.Equal(t, 8, len(stats), "Expected 8 results")
summary := &ConntrackStat{}
for i, exp := range slist.Items() {
st := stats[i]
assert.Equal(t, exp.Entries, st.Entries)
summary.Entries += st.Entries
assert.Equal(t, exp.Searched, st.Searched)
summary.Searched += st.Searched
assert.Equal(t, exp.Found, st.Found)
summary.Found += st.Found
assert.Equal(t, exp.New, st.New)
summary.New += st.New
assert.Equal(t, exp.Invalid, st.Invalid)
summary.Invalid += st.Invalid
assert.Equal(t, exp.Ignore, st.Ignore)
summary.Ignore += st.Ignore
assert.Equal(t, exp.Delete, st.Delete)
summary.Delete += st.Delete
assert.Equal(t, exp.DeleteList, st.DeleteList)
summary.DeleteList += st.DeleteList
assert.Equal(t, exp.Insert, st.Insert)
summary.Insert += st.Insert
assert.Equal(t, exp.InsertFailed, st.InsertFailed)
summary.InsertFailed += st.InsertFailed
assert.Equal(t, exp.Drop, st.Drop)
summary.Drop += st.Drop
assert.Equal(t, exp.EarlyDrop, st.EarlyDrop)
summary.EarlyDrop += st.EarlyDrop
assert.Equal(t, exp.IcmpError, st.IcmpError)
summary.IcmpError += st.IcmpError
assert.Equal(t, exp.ExpectNew, st.ExpectNew)
summary.ExpectNew += st.ExpectNew
assert.Equal(t, exp.ExpectCreate, st.ExpectCreate)
summary.ExpectCreate += st.ExpectCreate
assert.Equal(t, exp.ExpectDelete, st.ExpectDelete)
summary.ExpectDelete += st.ExpectDelete
assert.Equal(t, exp.SearchRestart, st.SearchRestart)
summary.SearchRestart += st.SearchRestart
}
// Test summary grouping
totals, err := conntrackStatsFromFile(tmpfile.Name(), false)
for i, st := range totals {
assert.Equal(t, summary.Entries, st.Entries)
assert.Equal(t, summary.Searched, st.Searched)
assert.Equal(t, summary.Found, st.Found)
assert.Equal(t, summary.New, st.New)
assert.Equal(t, summary.Invalid, st.Invalid)
assert.Equal(t, summary.Ignore, st.Ignore)
assert.Equal(t, summary.Delete, st.Delete)
assert.Equal(t, summary.DeleteList, st.DeleteList)
assert.Equal(t, summary.Insert, st.Insert)
assert.Equal(t, summary.InsertFailed, st.InsertFailed)
assert.Equal(t, summary.Drop, st.Drop)
assert.Equal(t, summary.EarlyDrop, st.EarlyDrop)
assert.Equal(t, summary.IcmpError, st.IcmpError)
assert.Equal(t, summary.ExpectNew, st.ExpectNew)
assert.Equal(t, summary.ExpectCreate, st.ExpectCreate)
assert.Equal(t, summary.ExpectDelete, st.ExpectDelete)
assert.Equal(t, summary.SearchRestart, st.SearchRestart)
assert.Equal(t, 0, i) // Should only have one element
}
}

View File

@ -156,6 +156,14 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for openbsd")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.

View File

@ -206,6 +206,15 @@ func FilterCountersWithContext(ctx context.Context) ([]FilterStat, error) {
return nil, errors.New("NetFilterCounters not implemented for windows")
}
func ConntrackStats(percpu bool) ([]ConntrackStat, error) {
return ConntrackStatsWithContext(context.Background(), percpu)
}
func ConntrackStatsWithContext(ctx context.Context, percpu bool) ([]ConntrackStat, error) {
return nil, common.ErrNotImplementedError
}
// NetProtoCounters returns network statistics for the entire system
// If protocols is empty then all protocols are returned, otherwise
// just the protocols in the list are returned.