mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-28 12:19:16 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			247 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			247 lines
		
	
	
		
			5.8 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| 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 local (virtual) port.
 | |
| 	LocalPort uint16
 | |
| 	// The local firewall mark
 | |
| 	LocalMark string
 | |
| 	// The transport protocol (TCP, UDP).
 | |
| 	Proto string
 | |
| 	// The remote (real) IP address.
 | |
| 	RemoteAddress net.IP
 | |
| 	// The remote (real) port.
 | |
| 	RemotePort uint16
 | |
| 	// 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(string(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
 | |
| }
 |