1// Copyright 2020 The Prometheus Authors
2// Licensed under the Apache License, Version 2.0 (the "License");
3// you may not use this file except in compliance with the License.
4// You may obtain a copy of the License at
5//
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package procfs
15
16import (
17	"bufio"
18	"bytes"
19	"fmt"
20	"io"
21	"strconv"
22	"strings"
23
24	"github.com/prometheus/procfs/internal/util"
25)
26
27// A ConntrackStatEntry represents one line from net/stat/nf_conntrack
28// and contains netfilter conntrack statistics at one CPU core
29type ConntrackStatEntry struct {
30	Entries       uint64
31	Found         uint64
32	Invalid       uint64
33	Ignore        uint64
34	Insert        uint64
35	InsertFailed  uint64
36	Drop          uint64
37	EarlyDrop     uint64
38	SearchRestart uint64
39}
40
41// ConntrackStat retrieves netfilter's conntrack statistics, split by CPU cores
42func (fs FS) ConntrackStat() ([]ConntrackStatEntry, error) {
43	return readConntrackStat(fs.proc.Path("net", "stat", "nf_conntrack"))
44}
45
46// Parses a slice of ConntrackStatEntries from the given filepath
47func readConntrackStat(path string) ([]ConntrackStatEntry, error) {
48	// This file is small and can be read with one syscall.
49	b, err := util.ReadFileNoStat(path)
50	if err != nil {
51		// Do not wrap this error so the caller can detect os.IsNotExist and
52		// similar conditions.
53		return nil, err
54	}
55
56	stat, err := parseConntrackStat(bytes.NewReader(b))
57	if err != nil {
58		return nil, fmt.Errorf("failed to read conntrack stats from %q: %w", path, err)
59	}
60
61	return stat, nil
62}
63
64// Reads the contents of a conntrack statistics file and parses a slice of ConntrackStatEntries
65func parseConntrackStat(r io.Reader) ([]ConntrackStatEntry, error) {
66	var entries []ConntrackStatEntry
67
68	scanner := bufio.NewScanner(r)
69	scanner.Scan()
70	for scanner.Scan() {
71		fields := strings.Fields(scanner.Text())
72		conntrackEntry, err := parseConntrackStatEntry(fields)
73		if err != nil {
74			return nil, err
75		}
76		entries = append(entries, *conntrackEntry)
77	}
78
79	return entries, nil
80}
81
82// Parses a ConntrackStatEntry from given array of fields
83func parseConntrackStatEntry(fields []string) (*ConntrackStatEntry, error) {
84	if len(fields) != 17 {
85		return nil, fmt.Errorf("invalid conntrackstat entry, missing fields")
86	}
87	entry := &ConntrackStatEntry{}
88
89	entries, err := parseConntrackStatField(fields[0])
90	if err != nil {
91		return nil, err
92	}
93	entry.Entries = entries
94
95	found, err := parseConntrackStatField(fields[2])
96	if err != nil {
97		return nil, err
98	}
99	entry.Found = found
100
101	invalid, err := parseConntrackStatField(fields[4])
102	if err != nil {
103		return nil, err
104	}
105	entry.Invalid = invalid
106
107	ignore, err := parseConntrackStatField(fields[5])
108	if err != nil {
109		return nil, err
110	}
111	entry.Ignore = ignore
112
113	insert, err := parseConntrackStatField(fields[8])
114	if err != nil {
115		return nil, err
116	}
117	entry.Insert = insert
118
119	insertFailed, err := parseConntrackStatField(fields[9])
120	if err != nil {
121		return nil, err
122	}
123	entry.InsertFailed = insertFailed
124
125	drop, err := parseConntrackStatField(fields[10])
126	if err != nil {
127		return nil, err
128	}
129	entry.Drop = drop
130
131	earlyDrop, err := parseConntrackStatField(fields[11])
132	if err != nil {
133		return nil, err
134	}
135	entry.EarlyDrop = earlyDrop
136
137	searchRestart, err := parseConntrackStatField(fields[16])
138	if err != nil {
139		return nil, err
140	}
141	entry.SearchRestart = searchRestart
142
143	return entry, nil
144}
145
146// Parses a uint64 from given hex in string
147func parseConntrackStatField(field string) (uint64, error) {
148	val, err := strconv.ParseUint(field, 16, 64)
149	if err != nil {
150		return 0, fmt.Errorf("couldn't parse %q field: %w", field, err)
151	}
152	return val, err
153}
154