Skip to content

Commit 8c052e3

Browse files
hubtwolandr
andauthored
support to read from /proc/net/tcp{,6} (#332)
* Add GIDs to ProcStatus struct Signed-off-by: Andrei Volnin <[email protected]> * support to read from /proc/net/tcp{,6} Signed-off-by: Hubert Chen <[email protected]> Co-authored-by: Andrei Volnin <[email protected]>
1 parent 9ad681c commit 8c052e3

7 files changed

+608
-273
lines changed

fixtures.ttar

+16-1
Original file line numberDiff line numberDiff line change
@@ -2204,10 +2204,25 @@ Lines: 1
22042204
00015c73 00020e76 F0000769 00000000
22052205
Mode: 644
22062206
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2207+
Path: fixtures/proc/net/tcp
2208+
Lines: 4
2209+
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
2210+
0: 0500000A:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
2211+
1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
2212+
2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
2213+
Mode: 644
2214+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
2215+
Path: fixtures/proc/net/tcp6
2216+
Lines: 3
2217+
sl local_address remote_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode ref pointer drops
2218+
1315: 00000000000000000000000000000000:14EB 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 981 0 21040 2 0000000013726323 0
2219+
6073: 000080FE00000000FFADE15609667CFE:C781 00000000000000000000000000000000:0000 07 00000000:00000000 00:00000000 00000000 1000 0 11337031 2 00000000b9256fdd 0
2220+
Mode: 644
2221+
# ttar - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
22072222
Path: fixtures/proc/net/udp
22082223
Lines: 4
22092224
sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
2210-
0: 0A000005:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
2225+
0: 0500000A:0016 00000000:0000 0A 00000000:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
22112226
1: 00000000:0016 00000000:0000 0A 00000001:00000000 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
22122227
2: 00000000:0016 00000000:0000 0A 00000001:00000001 00:00000000 00000000 0 0 2740 1 ffff88003d3af3c0 100 0 0 10 0
22132228
Mode: 644

net_ip_socket.go

+228
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
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+
14+
package procfs
15+
16+
import (
17+
"bufio"
18+
"encoding/hex"
19+
"fmt"
20+
"io"
21+
"net"
22+
"os"
23+
"strconv"
24+
"strings"
25+
)
26+
27+
const (
28+
// readLimit is used by io.LimitReader while reading the content of the
29+
// /proc/net/udp{,6} files. The number of lines inside such a file is dynamic
30+
// as each line represents a single used socket.
31+
// In theory, the number of available sockets is 65535 (2^16 - 1) per IP.
32+
// With e.g. 150 Byte per line and the maximum number of 65535,
33+
// the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP.
34+
readLimit = 4294967296 // Byte -> 4 GiB
35+
)
36+
37+
// this contains generic data structures for both udp and tcp sockets
38+
type (
39+
// NetIPSocket represents the contents of /proc/net/{t,u}dp{,6} file without the header.
40+
NetIPSocket []*netIPSocketLine
41+
42+
// NetIPSocketSummary provides already computed values like the total queue lengths or
43+
// the total number of used sockets. In contrast to NetIPSocket it does not collect
44+
// the parsed lines into a slice.
45+
NetIPSocketSummary struct {
46+
// TxQueueLength shows the total queue length of all parsed tx_queue lengths.
47+
TxQueueLength uint64
48+
// RxQueueLength shows the total queue length of all parsed rx_queue lengths.
49+
RxQueueLength uint64
50+
// UsedSockets shows the total number of parsed lines representing the
51+
// number of used sockets.
52+
UsedSockets uint64
53+
}
54+
55+
// netIPSocketLine represents the fields parsed from a single line
56+
// in /proc/net/{t,u}dp{,6}. Fields which are not used by IPSocket are skipped.
57+
// For the proc file format details, see https://linux.die.net/man/5/proc.
58+
netIPSocketLine struct {
59+
Sl uint64
60+
LocalAddr net.IP
61+
LocalPort uint64
62+
RemAddr net.IP
63+
RemPort uint64
64+
St uint64
65+
TxQueue uint64
66+
RxQueue uint64
67+
UID uint64
68+
}
69+
)
70+
71+
func newNetIPSocket(file string) (NetIPSocket, error) {
72+
f, err := os.Open(file)
73+
if err != nil {
74+
return nil, err
75+
}
76+
defer f.Close()
77+
78+
var netIPSocket NetIPSocket
79+
80+
lr := io.LimitReader(f, readLimit)
81+
s := bufio.NewScanner(lr)
82+
s.Scan() // skip first line with headers
83+
for s.Scan() {
84+
fields := strings.Fields(s.Text())
85+
line, err := parseNetIPSocketLine(fields)
86+
if err != nil {
87+
return nil, err
88+
}
89+
netIPSocket = append(netIPSocket, line)
90+
}
91+
if err := s.Err(); err != nil {
92+
return nil, err
93+
}
94+
return netIPSocket, nil
95+
}
96+
97+
// newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file.
98+
func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
99+
f, err := os.Open(file)
100+
if err != nil {
101+
return nil, err
102+
}
103+
defer f.Close()
104+
105+
var netIPSocketSummary NetIPSocketSummary
106+
107+
lr := io.LimitReader(f, readLimit)
108+
s := bufio.NewScanner(lr)
109+
s.Scan() // skip first line with headers
110+
for s.Scan() {
111+
fields := strings.Fields(s.Text())
112+
line, err := parseNetIPSocketLine(fields)
113+
if err != nil {
114+
return nil, err
115+
}
116+
netIPSocketSummary.TxQueueLength += line.TxQueue
117+
netIPSocketSummary.RxQueueLength += line.RxQueue
118+
netIPSocketSummary.UsedSockets++
119+
}
120+
if err := s.Err(); err != nil {
121+
return nil, err
122+
}
123+
return &netIPSocketSummary, nil
124+
}
125+
126+
// the /proc/net/{t,u}dp{,6} files are network byte order for ipv4 and for ipv6 the address is four words consisting of four bytes each. In each of those four words the four bytes are written in reverse order.
127+
128+
func parseIP(hexIP string) (net.IP, error) {
129+
var byteIP []byte
130+
byteIP, err := hex.DecodeString(hexIP)
131+
if err != nil {
132+
return nil, fmt.Errorf(
133+
"cannot parse address field in socket line: %s", hexIP)
134+
}
135+
switch len(byteIP) {
136+
case 4:
137+
return net.IP{byteIP[3], byteIP[2], byteIP[1], byteIP[0]}, nil
138+
case 16:
139+
i := net.IP{
140+
byteIP[3], byteIP[2], byteIP[1], byteIP[0],
141+
byteIP[7], byteIP[6], byteIP[5], byteIP[4],
142+
byteIP[11], byteIP[10], byteIP[9], byteIP[8],
143+
byteIP[15], byteIP[14], byteIP[13], byteIP[12],
144+
}
145+
return i, nil
146+
default:
147+
return nil, fmt.Errorf("Unable to parse IP %s", hexIP)
148+
}
149+
}
150+
151+
// parseNetIPSocketLine parses a single line, represented by a list of fields.
152+
func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
153+
line := &netIPSocketLine{}
154+
if len(fields) < 8 {
155+
return nil, fmt.Errorf(
156+
"cannot parse net socket line as it has less then 8 columns: %s",
157+
strings.Join(fields, " "),
158+
)
159+
}
160+
var err error // parse error
161+
162+
// sl
163+
s := strings.Split(fields[0], ":")
164+
if len(s) != 2 {
165+
return nil, fmt.Errorf(
166+
"cannot parse sl field in socket line: %s", fields[0])
167+
}
168+
169+
if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
170+
return nil, fmt.Errorf("cannot parse sl value in socket line: %s", err)
171+
}
172+
// local_address
173+
l := strings.Split(fields[1], ":")
174+
if len(l) != 2 {
175+
return nil, fmt.Errorf(
176+
"cannot parse local_address field in socket line: %s", fields[1])
177+
}
178+
if line.LocalAddr, err = parseIP(l[0]); err != nil {
179+
return nil, err
180+
}
181+
if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
182+
return nil, fmt.Errorf(
183+
"cannot parse local_address port value in socket line: %s", err)
184+
}
185+
186+
// remote_address
187+
r := strings.Split(fields[2], ":")
188+
if len(r) != 2 {
189+
return nil, fmt.Errorf(
190+
"cannot parse rem_address field in socket line: %s", fields[1])
191+
}
192+
if line.RemAddr, err = parseIP(r[0]); err != nil {
193+
return nil, err
194+
}
195+
if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
196+
return nil, fmt.Errorf(
197+
"cannot parse rem_address port value in socket line: %s", err)
198+
}
199+
200+
// st
201+
if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
202+
return nil, fmt.Errorf(
203+
"cannot parse st value in socket line: %s", err)
204+
}
205+
206+
// tx_queue and rx_queue
207+
q := strings.Split(fields[4], ":")
208+
if len(q) != 2 {
209+
return nil, fmt.Errorf(
210+
"cannot parse tx/rx queues in socket line as it has a missing colon: %s",
211+
fields[4],
212+
)
213+
}
214+
if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
215+
return nil, fmt.Errorf("cannot parse tx_queue value in socket line: %s", err)
216+
}
217+
if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
218+
return nil, fmt.Errorf("cannot parse rx_queue value in socket line: %s", err)
219+
}
220+
221+
// uid
222+
if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
223+
return nil, fmt.Errorf(
224+
"cannot parse uid value in socket line: %s", err)
225+
}
226+
227+
return line, nil
228+
}

net_ip_socket_test.go

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
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+
14+
package procfs
15+
16+
import (
17+
"net"
18+
"reflect"
19+
"testing"
20+
)
21+
22+
func Test_parseNetIPSocketLine(t *testing.T) {
23+
tests := []struct {
24+
fields []string
25+
name string
26+
want *netIPSocketLine
27+
wantErr bool
28+
}{
29+
{
30+
name: "reading valid lines, no issue should happened",
31+
fields: []string{"11:", "00000000:0000", "00000000:0000", "0A", "00000017:0000002A", "0:0", "0", "1000"},
32+
want: &netIPSocketLine{
33+
Sl: 11,
34+
LocalAddr: net.IP{0, 0, 0, 0},
35+
LocalPort: 0,
36+
RemAddr: net.IP{0, 0, 0, 0},
37+
RemPort: 0,
38+
St: 10,
39+
TxQueue: 23,
40+
RxQueue: 42,
41+
UID: 1000,
42+
},
43+
},
44+
{
45+
name: "error case - invalid line - number of fields/columns < 8",
46+
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0:0", "0"},
47+
want: nil,
48+
wantErr: true,
49+
},
50+
{
51+
name: "error case - parse sl - not a valid uint",
52+
fields: []string{"a:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
53+
want: nil,
54+
wantErr: true,
55+
},
56+
{
57+
name: "error case - parse local_address - not a valid hex",
58+
fields: []string{"1:", "0000000O:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "0"},
59+
want: nil,
60+
wantErr: true,
61+
},
62+
{
63+
name: "error case - parse rem_address - not a valid hex",
64+
fields: []string{"1:", "00000000:0000", "0000000O:0000", "07", "00000000:00000001", "0:0", "0", "0"},
65+
want: nil,
66+
wantErr: true,
67+
},
68+
{
69+
name: "error case - cannot parse line - missing colon",
70+
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "0000000000000001", "0:0", "0", "0"},
71+
want: nil,
72+
wantErr: true,
73+
},
74+
{
75+
name: "error case - parse tx_queue - not a valid hex",
76+
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "DEADCODE:00000001", "0:0", "0", "0"},
77+
want: nil,
78+
wantErr: true,
79+
},
80+
{
81+
name: "error case - parse rx_queue - not a valid hex",
82+
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:FEEDCODE", "0:0", "0", "0"},
83+
want: nil,
84+
wantErr: true,
85+
},
86+
{
87+
name: "error case - parse UID - not a valid uint",
88+
fields: []string{"1:", "00000000:0000", "00000000:0000", "07", "00000000:00000001", "0:0", "0", "-10"},
89+
want: nil,
90+
wantErr: true,
91+
},
92+
}
93+
for _, tt := range tests {
94+
t.Run(tt.name, func(t *testing.T) {
95+
got, err := parseNetIPSocketLine(tt.fields)
96+
if (err != nil) != tt.wantErr {
97+
t.Errorf("parseNetIPSocketLine() error = %v, wantErr %v", err, tt.wantErr)
98+
return
99+
}
100+
if tt.want == nil && got != nil {
101+
t.Errorf("parseNetIPSocketLine() = %v, want %v", got, tt.want)
102+
}
103+
if !reflect.DeepEqual(got, tt.want) {
104+
t.Errorf("parseNetIPSocketLine() = %#v, want %#v", got, tt.want)
105+
}
106+
})
107+
}
108+
}

0 commit comments

Comments
 (0)