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	"bytes"
19	"encoding/hex"
20	"errors"
21	"fmt"
22	"io"
23	"io/ioutil"
24	"net"
25	"os"
26	"strconv"
27	"strings"
28
29	"github.com/prometheus/procfs/internal/util"
30)
31
32// IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`.
33type IPVSStats struct {
34	// Total count of connections.
35	Connections uint64
36	// Total incoming packages processed.
37	IncomingPackets uint64
38	// Total outgoing packages processed.
39	OutgoingPackets uint64
40	// Total incoming traffic.
41	IncomingBytes uint64
42	// Total outgoing traffic.
43	OutgoingBytes uint64
44}
45
46// IPVSBackendStatus holds current metrics of one virtual / real address pair.
47type IPVSBackendStatus struct {
48	// The local (virtual) IP address.
49	LocalAddress net.IP
50	// The remote (real) IP address.
51	RemoteAddress net.IP
52	// The local (virtual) port.
53	LocalPort uint16
54	// The remote (real) port.
55	RemotePort uint16
56	// The local firewall mark
57	LocalMark string
58	// The transport protocol (TCP, UDP).
59	Proto string
60	// The current number of active connections for this virtual/real address pair.
61	ActiveConn uint64
62	// The current number of inactive connections for this virtual/real address pair.
63	InactConn uint64
64	// The current weight of this virtual/real address pair.
65	Weight uint64
66}
67
68// IPVSStats reads the IPVS statistics from the specified `proc` filesystem.
69func (fs FS) IPVSStats() (IPVSStats, error) {
70	data, err := util.ReadFileNoStat(fs.proc.Path("net/ip_vs_stats"))
71	if err != nil {
72		return IPVSStats{}, err
73	}
74
75	return parseIPVSStats(bytes.NewReader(data))
76}
77
78// parseIPVSStats performs the actual parsing of `ip_vs_stats`.
79func parseIPVSStats(r io.Reader) (IPVSStats, error) {
80	var (
81		statContent []byte
82		statLines   []string
83		statFields  []string
84		stats       IPVSStats
85	)
86
87	statContent, err := ioutil.ReadAll(r)
88	if err != nil {
89		return IPVSStats{}, err
90	}
91
92	statLines = strings.SplitN(string(statContent), "\n", 4)
93	if len(statLines) != 4 {
94		return IPVSStats{}, errors.New("ip_vs_stats corrupt: too short")
95	}
96
97	statFields = strings.Fields(statLines[2])
98	if len(statFields) != 5 {
99		return IPVSStats{}, errors.New("ip_vs_stats corrupt: unexpected number of fields")
100	}
101
102	stats.Connections, err = strconv.ParseUint(statFields[0], 16, 64)
103	if err != nil {
104		return IPVSStats{}, err
105	}
106	stats.IncomingPackets, err = strconv.ParseUint(statFields[1], 16, 64)
107	if err != nil {
108		return IPVSStats{}, err
109	}
110	stats.OutgoingPackets, err = strconv.ParseUint(statFields[2], 16, 64)
111	if err != nil {
112		return IPVSStats{}, err
113	}
114	stats.IncomingBytes, err = strconv.ParseUint(statFields[3], 16, 64)
115	if err != nil {
116		return IPVSStats{}, err
117	}
118	stats.OutgoingBytes, err = strconv.ParseUint(statFields[4], 16, 64)
119	if err != nil {
120		return IPVSStats{}, err
121	}
122
123	return stats, nil
124}
125
126// IPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem.
127func (fs FS) IPVSBackendStatus() ([]IPVSBackendStatus, error) {
128	file, err := os.Open(fs.proc.Path("net/ip_vs"))
129	if err != nil {
130		return nil, err
131	}
132	defer file.Close()
133
134	return parseIPVSBackendStatus(file)
135}
136
137func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) {
138	var (
139		status       []IPVSBackendStatus
140		scanner      = bufio.NewScanner(file)
141		proto        string
142		localMark    string
143		localAddress net.IP
144		localPort    uint16
145		err          error
146	)
147
148	for scanner.Scan() {
149		fields := strings.Fields(scanner.Text())
150		if len(fields) == 0 {
151			continue
152		}
153		switch {
154		case fields[0] == "IP" || fields[0] == "Prot" || fields[1] == "RemoteAddress:Port":
155			continue
156		case fields[0] == "TCP" || fields[0] == "UDP":
157			if len(fields) < 2 {
158				continue
159			}
160			proto = fields[0]
161			localMark = ""
162			localAddress, localPort, err = parseIPPort(fields[1])
163			if err != nil {
164				return nil, err
165			}
166		case fields[0] == "FWM":
167			if len(fields) < 2 {
168				continue
169			}
170			proto = fields[0]
171			localMark = fields[1]
172			localAddress = nil
173			localPort = 0
174		case fields[0] == "->":
175			if len(fields) < 6 {
176				continue
177			}
178			remoteAddress, remotePort, err := parseIPPort(fields[1])
179			if err != nil {
180				return nil, err
181			}
182			weight, err := strconv.ParseUint(fields[3], 10, 64)
183			if err != nil {
184				return nil, err
185			}
186			activeConn, err := strconv.ParseUint(fields[4], 10, 64)
187			if err != nil {
188				return nil, err
189			}
190			inactConn, err := strconv.ParseUint(fields[5], 10, 64)
191			if err != nil {
192				return nil, err
193			}
194			status = append(status, IPVSBackendStatus{
195				LocalAddress:  localAddress,
196				LocalPort:     localPort,
197				LocalMark:     localMark,
198				RemoteAddress: remoteAddress,
199				RemotePort:    remotePort,
200				Proto:         proto,
201				Weight:        weight,
202				ActiveConn:    activeConn,
203				InactConn:     inactConn,
204			})
205		}
206	}
207	return status, nil
208}
209
210func parseIPPort(s string) (net.IP, uint16, error) {
211	var (
212		ip  net.IP
213		err error
214	)
215
216	switch len(s) {
217	case 13:
218		ip, err = hex.DecodeString(s[0:8])
219		if err != nil {
220			return nil, 0, err
221		}
222	case 46:
223		ip = net.ParseIP(s[1:40])
224		if ip == nil {
225			return nil, 0, fmt.Errorf("invalid IPv6 address: %s", s[1:40])
226		}
227	default:
228		return nil, 0, fmt.Errorf("unexpected IP:Port: %s", s)
229	}
230
231	portString := s[len(s)-4:]
232	if len(portString) != 4 {
233		return nil, 0, fmt.Errorf("unexpected port string format: %s", portString)
234	}
235	port, err := strconv.ParseUint(portString, 16, 16)
236	if err != nil {
237		return nil, 0, err
238	}
239
240	return ip, uint16(port), nil
241}
242