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	"encoding/hex"
19	"fmt"
20	"io"
21	"net"
22	"os"
23	"strconv"
24	"strings"
25)
26
27const (
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
38type (
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		Inode     uint64
69	}
70)
71
72func newNetIPSocket(file string) (NetIPSocket, error) {
73	f, err := os.Open(file)
74	if err != nil {
75		return nil, err
76	}
77	defer f.Close()
78
79	var netIPSocket NetIPSocket
80
81	lr := io.LimitReader(f, readLimit)
82	s := bufio.NewScanner(lr)
83	s.Scan() // skip first line with headers
84	for s.Scan() {
85		fields := strings.Fields(s.Text())
86		line, err := parseNetIPSocketLine(fields)
87		if err != nil {
88			return nil, err
89		}
90		netIPSocket = append(netIPSocket, line)
91	}
92	if err := s.Err(); err != nil {
93		return nil, err
94	}
95	return netIPSocket, nil
96}
97
98// newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file.
99func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) {
100	f, err := os.Open(file)
101	if err != nil {
102		return nil, err
103	}
104	defer f.Close()
105
106	var netIPSocketSummary NetIPSocketSummary
107
108	lr := io.LimitReader(f, readLimit)
109	s := bufio.NewScanner(lr)
110	s.Scan() // skip first line with headers
111	for s.Scan() {
112		fields := strings.Fields(s.Text())
113		line, err := parseNetIPSocketLine(fields)
114		if err != nil {
115			return nil, err
116		}
117		netIPSocketSummary.TxQueueLength += line.TxQueue
118		netIPSocketSummary.RxQueueLength += line.RxQueue
119		netIPSocketSummary.UsedSockets++
120	}
121	if err := s.Err(); err != nil {
122		return nil, err
123	}
124	return &netIPSocketSummary, nil
125}
126
127// 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.
128
129func parseIP(hexIP string) (net.IP, error) {
130	var byteIP []byte
131	byteIP, err := hex.DecodeString(hexIP)
132	if err != nil {
133		return nil, fmt.Errorf("cannot parse address field in socket line %q", 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.
152func parseNetIPSocketLine(fields []string) (*netIPSocketLine, error) {
153	line := &netIPSocketLine{}
154	if len(fields) < 10 {
155		return nil, fmt.Errorf(
156			"cannot parse net socket line as it has less then 10 columns %q",
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("cannot parse sl field in socket line %q", fields[0])
166	}
167
168	if line.Sl, err = strconv.ParseUint(s[0], 0, 64); err != nil {
169		return nil, fmt.Errorf("cannot parse sl value in socket line: %w", err)
170	}
171	// local_address
172	l := strings.Split(fields[1], ":")
173	if len(l) != 2 {
174		return nil, fmt.Errorf("cannot parse local_address field in socket line %q", fields[1])
175	}
176	if line.LocalAddr, err = parseIP(l[0]); err != nil {
177		return nil, err
178	}
179	if line.LocalPort, err = strconv.ParseUint(l[1], 16, 64); err != nil {
180		return nil, fmt.Errorf("cannot parse local_address port value in socket line: %w", err)
181	}
182
183	// remote_address
184	r := strings.Split(fields[2], ":")
185	if len(r) != 2 {
186		return nil, fmt.Errorf("cannot parse rem_address field in socket line %q", fields[1])
187	}
188	if line.RemAddr, err = parseIP(r[0]); err != nil {
189		return nil, err
190	}
191	if line.RemPort, err = strconv.ParseUint(r[1], 16, 64); err != nil {
192		return nil, fmt.Errorf("cannot parse rem_address port value in socket line: %w", err)
193	}
194
195	// st
196	if line.St, err = strconv.ParseUint(fields[3], 16, 64); err != nil {
197		return nil, fmt.Errorf("cannot parse st value in socket line: %w", err)
198	}
199
200	// tx_queue and rx_queue
201	q := strings.Split(fields[4], ":")
202	if len(q) != 2 {
203		return nil, fmt.Errorf(
204			"cannot parse tx/rx queues in socket line as it has a missing colon %q",
205			fields[4],
206		)
207	}
208	if line.TxQueue, err = strconv.ParseUint(q[0], 16, 64); err != nil {
209		return nil, fmt.Errorf("cannot parse tx_queue value in socket line: %w", err)
210	}
211	if line.RxQueue, err = strconv.ParseUint(q[1], 16, 64); err != nil {
212		return nil, fmt.Errorf("cannot parse rx_queue value in socket line: %w", err)
213	}
214
215	// uid
216	if line.UID, err = strconv.ParseUint(fields[7], 0, 64); err != nil {
217		return nil, fmt.Errorf("cannot parse uid value in socket line: %w", err)
218	}
219
220	// inode
221	if line.Inode, err = strconv.ParseUint(fields[9], 0, 64); err != nil {
222		return nil, fmt.Errorf("cannot parse inode value in socket line: %w", err)
223	}
224
225	return line, nil
226}
227