| // Copyright 2018 The Prometheus Authors |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package procfs |
| |
| import ( |
| "bufio" |
| "encoding/hex" |
| "errors" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "net" |
| "os" |
| "strconv" |
| "strings" |
| ) |
| |
| // IPVSStats holds IPVS statistics, as exposed by the kernel in `/proc/net/ip_vs_stats`. |
| type IPVSStats struct { |
| // Total count of connections. |
| Connections uint64 |
| // Total incoming packages processed. |
| IncomingPackets uint64 |
| // Total outgoing packages processed. |
| OutgoingPackets uint64 |
| // Total incoming traffic. |
| IncomingBytes uint64 |
| // Total outgoing traffic. |
| OutgoingBytes uint64 |
| } |
| |
| // IPVSBackendStatus holds current metrics of one virtual / real address pair. |
| type IPVSBackendStatus struct { |
| // The local (virtual) IP address. |
| LocalAddress net.IP |
| // The remote (real) IP address. |
| RemoteAddress net.IP |
| // The local (virtual) port. |
| LocalPort uint16 |
| // The remote (real) port. |
| RemotePort uint16 |
| // The local firewall mark |
| LocalMark string |
| // The transport protocol (TCP, UDP). |
| Proto string |
| // The current number of active connections for this virtual/real address pair. |
| ActiveConn uint64 |
| // The current number of inactive connections for this virtual/real address pair. |
| InactConn uint64 |
| // The current weight of this virtual/real address pair. |
| Weight uint64 |
| } |
| |
| // NewIPVSStats reads the IPVS statistics. |
| func NewIPVSStats() (IPVSStats, error) { |
| fs, err := NewFS(DefaultMountPoint) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| |
| return fs.NewIPVSStats() |
| } |
| |
| // NewIPVSStats reads the IPVS statistics from the specified `proc` filesystem. |
| func (fs FS) NewIPVSStats() (IPVSStats, error) { |
| file, err := os.Open(fs.Path("net/ip_vs_stats")) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| defer file.Close() |
| |
| return parseIPVSStats(file) |
| } |
| |
| // parseIPVSStats performs the actual parsing of `ip_vs_stats`. |
| func parseIPVSStats(file io.Reader) (IPVSStats, error) { |
| var ( |
| statContent []byte |
| statLines []string |
| statFields []string |
| stats IPVSStats |
| ) |
| |
| statContent, err := ioutil.ReadAll(file) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| |
| statLines = strings.SplitN(string(statContent), "\n", 4) |
| if len(statLines) != 4 { |
| return IPVSStats{}, errors.New("ip_vs_stats corrupt: too short") |
| } |
| |
| statFields = strings.Fields(statLines[2]) |
| if len(statFields) != 5 { |
| return IPVSStats{}, errors.New("ip_vs_stats corrupt: unexpected number of fields") |
| } |
| |
| stats.Connections, err = strconv.ParseUint(statFields[0], 16, 64) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| stats.IncomingPackets, err = strconv.ParseUint(statFields[1], 16, 64) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| stats.OutgoingPackets, err = strconv.ParseUint(statFields[2], 16, 64) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| stats.IncomingBytes, err = strconv.ParseUint(statFields[3], 16, 64) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| stats.OutgoingBytes, err = strconv.ParseUint(statFields[4], 16, 64) |
| if err != nil { |
| return IPVSStats{}, err |
| } |
| |
| return stats, nil |
| } |
| |
| // NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs. |
| func NewIPVSBackendStatus() ([]IPVSBackendStatus, error) { |
| fs, err := NewFS(DefaultMountPoint) |
| if err != nil { |
| return []IPVSBackendStatus{}, err |
| } |
| |
| return fs.NewIPVSBackendStatus() |
| } |
| |
| // NewIPVSBackendStatus reads and returns the status of all (virtual,real) server pairs from the specified `proc` filesystem. |
| func (fs FS) NewIPVSBackendStatus() ([]IPVSBackendStatus, error) { |
| file, err := os.Open(fs.Path("net/ip_vs")) |
| if err != nil { |
| return nil, err |
| } |
| defer file.Close() |
| |
| return parseIPVSBackendStatus(file) |
| } |
| |
| func parseIPVSBackendStatus(file io.Reader) ([]IPVSBackendStatus, error) { |
| var ( |
| status []IPVSBackendStatus |
| scanner = bufio.NewScanner(file) |
| proto string |
| localMark string |
| localAddress net.IP |
| localPort uint16 |
| err error |
| ) |
| |
| for scanner.Scan() { |
| fields := strings.Fields(scanner.Text()) |
| if len(fields) == 0 { |
| continue |
| } |
| switch { |
| case fields[0] == "IP" || fields[0] == "Prot" || fields[1] == "RemoteAddress:Port": |
| continue |
| case fields[0] == "TCP" || fields[0] == "UDP": |
| if len(fields) < 2 { |
| continue |
| } |
| proto = fields[0] |
| localMark = "" |
| localAddress, localPort, err = parseIPPort(fields[1]) |
| if err != nil { |
| return nil, err |
| } |
| case fields[0] == "FWM": |
| if len(fields) < 2 { |
| continue |
| } |
| proto = fields[0] |
| localMark = fields[1] |
| localAddress = nil |
| localPort = 0 |
| case fields[0] == "->": |
| if len(fields) < 6 { |
| continue |
| } |
| remoteAddress, remotePort, err := parseIPPort(fields[1]) |
| if err != nil { |
| return nil, err |
| } |
| weight, err := strconv.ParseUint(fields[3], 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| activeConn, err := strconv.ParseUint(fields[4], 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| inactConn, err := strconv.ParseUint(fields[5], 10, 64) |
| if err != nil { |
| return nil, err |
| } |
| status = append(status, IPVSBackendStatus{ |
| LocalAddress: localAddress, |
| LocalPort: localPort, |
| LocalMark: localMark, |
| RemoteAddress: remoteAddress, |
| RemotePort: remotePort, |
| Proto: proto, |
| Weight: weight, |
| ActiveConn: activeConn, |
| InactConn: inactConn, |
| }) |
| } |
| } |
| return status, nil |
| } |
| |
| func parseIPPort(s string) (net.IP, uint16, error) { |
| var ( |
| ip net.IP |
| err error |
| ) |
| |
| switch len(s) { |
| case 13: |
| ip, err = hex.DecodeString(s[0:8]) |
| if err != nil { |
| return nil, 0, err |
| } |
| case 46: |
| ip = net.ParseIP(s[1:40]) |
| if ip == nil { |
| return nil, 0, fmt.Errorf("invalid IPv6 address: %s", s[1:40]) |
| } |
| default: |
| return nil, 0, fmt.Errorf("unexpected IP:Port: %s", s) |
| } |
| |
| portString := s[len(s)-4:] |
| if len(portString) != 4 { |
| return nil, 0, fmt.Errorf("unexpected port string format: %s", portString) |
| } |
| port, err := strconv.ParseUint(portString, 16, 16) |
| if err != nil { |
| return nil, 0, err |
| } |
| |
| return ip, uint16(port), nil |
| } |