1// Copyright 2018 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	"fmt"
19	"io"
20	"os"
21	"strconv"
22	"strings"
23)
24
25// For the proc file format details,
26// see https://elixir.bootlin.com/linux/v4.17/source/net/unix/af_unix.c#L2815
27// and https://elixir.bootlin.com/linux/latest/source/include/uapi/linux/net.h#L48.
28
29// Constants for the various /proc/net/unix enumerations.
30// TODO: match against x/sys/unix or similar?
31const (
32	netUnixTypeStream    = 1
33	netUnixTypeDgram     = 2
34	netUnixTypeSeqpacket = 5
35
36	netUnixFlagDefault = 0
37	netUnixFlagListen  = 1 << 16
38
39	netUnixStateUnconnected  = 1
40	netUnixStateConnecting   = 2
41	netUnixStateConnected    = 3
42	netUnixStateDisconnected = 4
43)
44
45// NetUNIXType is the type of the type field.
46type NetUNIXType uint64
47
48// NetUNIXFlags is the type of the flags field.
49type NetUNIXFlags uint64
50
51// NetUNIXState is the type of the state field.
52type NetUNIXState uint64
53
54// NetUNIXLine represents a line of /proc/net/unix.
55type NetUNIXLine struct {
56	KernelPtr string
57	RefCount  uint64
58	Protocol  uint64
59	Flags     NetUNIXFlags
60	Type      NetUNIXType
61	State     NetUNIXState
62	Inode     uint64
63	Path      string
64}
65
66// NetUNIX holds the data read from /proc/net/unix.
67type NetUNIX struct {
68	Rows []*NetUNIXLine
69}
70
71// NetUNIX returns data read from /proc/net/unix.
72func (fs FS) NetUNIX() (*NetUNIX, error) {
73	return readNetUNIX(fs.proc.Path("net/unix"))
74}
75
76// readNetUNIX reads data in /proc/net/unix format from the specified file.
77func readNetUNIX(file string) (*NetUNIX, error) {
78	// This file could be quite large and a streaming read is desirable versus
79	// reading the entire contents at once.
80	f, err := os.Open(file)
81	if err != nil {
82		return nil, err
83	}
84	defer f.Close()
85
86	return parseNetUNIX(f)
87}
88
89// parseNetUNIX creates a NetUnix structure from the incoming stream.
90func parseNetUNIX(r io.Reader) (*NetUNIX, error) {
91	// Begin scanning by checking for the existence of Inode.
92	s := bufio.NewScanner(r)
93	s.Scan()
94
95	// From the man page of proc(5), it does not contain an Inode field,
96	// but in actually it exists. This code works for both cases.
97	hasInode := strings.Contains(s.Text(), "Inode")
98
99	// Expect a minimum number of fields, but Inode and Path are optional:
100	// Num       RefCount Protocol Flags    Type St Inode Path
101	minFields := 6
102	if hasInode {
103		minFields++
104	}
105
106	var nu NetUNIX
107	for s.Scan() {
108		line := s.Text()
109		item, err := nu.parseLine(line, hasInode, minFields)
110		if err != nil {
111			return nil, fmt.Errorf("failed to parse /proc/net/unix data %q: %w", line, err)
112		}
113
114		nu.Rows = append(nu.Rows, item)
115	}
116
117	if err := s.Err(); err != nil {
118		return nil, fmt.Errorf("failed to scan /proc/net/unix data: %w", err)
119	}
120
121	return &nu, nil
122}
123
124func (u *NetUNIX) parseLine(line string, hasInode bool, min int) (*NetUNIXLine, error) {
125	fields := strings.Fields(line)
126
127	l := len(fields)
128	if l < min {
129		return nil, fmt.Errorf("expected at least %d fields but got %d", min, l)
130	}
131
132	// Field offsets are as follows:
133	// Num       RefCount Protocol Flags    Type St Inode Path
134
135	kernelPtr := strings.TrimSuffix(fields[0], ":")
136
137	users, err := u.parseUsers(fields[1])
138	if err != nil {
139		return nil, fmt.Errorf("failed to parse ref count %q: %w", fields[1], err)
140	}
141
142	flags, err := u.parseFlags(fields[3])
143	if err != nil {
144		return nil, fmt.Errorf("failed to parse flags %q: %w", fields[3], err)
145	}
146
147	typ, err := u.parseType(fields[4])
148	if err != nil {
149		return nil, fmt.Errorf("failed to parse type %q: %w", fields[4], err)
150	}
151
152	state, err := u.parseState(fields[5])
153	if err != nil {
154		return nil, fmt.Errorf("failed to parse state %q: %w", fields[5], err)
155	}
156
157	var inode uint64
158	if hasInode {
159		inode, err = u.parseInode(fields[6])
160		if err != nil {
161			return nil, fmt.Errorf("failed to parse inode %q: %w", fields[6], err)
162		}
163	}
164
165	n := &NetUNIXLine{
166		KernelPtr: kernelPtr,
167		RefCount:  users,
168		Type:      typ,
169		Flags:     flags,
170		State:     state,
171		Inode:     inode,
172	}
173
174	// Path field is optional.
175	if l > min {
176		// Path occurs at either index 6 or 7 depending on whether inode is
177		// already present.
178		pathIdx := 7
179		if !hasInode {
180			pathIdx--
181		}
182
183		n.Path = fields[pathIdx]
184	}
185
186	return n, nil
187}
188
189func (u NetUNIX) parseUsers(s string) (uint64, error) {
190	return strconv.ParseUint(s, 16, 32)
191}
192
193func (u NetUNIX) parseType(s string) (NetUNIXType, error) {
194	typ, err := strconv.ParseUint(s, 16, 16)
195	if err != nil {
196		return 0, err
197	}
198
199	return NetUNIXType(typ), nil
200}
201
202func (u NetUNIX) parseFlags(s string) (NetUNIXFlags, error) {
203	flags, err := strconv.ParseUint(s, 16, 32)
204	if err != nil {
205		return 0, err
206	}
207
208	return NetUNIXFlags(flags), nil
209}
210
211func (u NetUNIX) parseState(s string) (NetUNIXState, error) {
212	st, err := strconv.ParseInt(s, 16, 8)
213	if err != nil {
214		return 0, err
215	}
216
217	return NetUNIXState(st), nil
218}
219
220func (u NetUNIX) parseInode(s string) (uint64, error) {
221	return strconv.ParseUint(s, 10, 64)
222}
223
224func (t NetUNIXType) String() string {
225	switch t {
226	case netUnixTypeStream:
227		return "stream"
228	case netUnixTypeDgram:
229		return "dgram"
230	case netUnixTypeSeqpacket:
231		return "seqpacket"
232	}
233	return "unknown"
234}
235
236func (f NetUNIXFlags) String() string {
237	switch f {
238	case netUnixFlagListen:
239		return "listen"
240	default:
241		return "default"
242	}
243}
244
245func (s NetUNIXState) String() string {
246	switch s {
247	case netUnixStateUnconnected:
248		return "unconnected"
249	case netUnixStateConnecting:
250		return "connecting"
251	case netUnixStateConnected:
252		return "connected"
253	case netUnixStateDisconnected:
254		return "disconnected"
255	}
256	return "unknown"
257}
258