diff --git a/net_ip_socket.go b/net_ip_socket.go index 679a91eb..b70f1fc7 100644 --- a/net_ip_socket.go +++ b/net_ip_socket.go @@ -14,14 +14,26 @@ package procfs import ( + "bufio" "encoding/hex" "fmt" + "io" "net" "os" "strconv" "strings" ) +const ( + // readLimit is used by io.LimitReader while reading the content of the + // /proc/net/udp{,6} files. The number of lines inside such a file is dynamic + // as each line represents a single used socket. + // In theory, the number of available sockets is 65535 (2^16 - 1) per IP. + // With e.g. 150 Byte per line and the maximum number of 65535, + // the reader needs to handle 150 Byte * 65535 =~ 10 MB for a single IP. + readLimit = 4294967296 // Byte -> 4 GiB +) + // This contains generic data structures for both udp and tcp sockets. type ( // NetIPSocket represents the contents of /proc/net/{t,u}dp{,6} file without the header. @@ -62,50 +74,49 @@ type ( ) func newNetIPSocket(file string) (NetIPSocket, error) { - var netIPSocket NetIPSocket - isUDP := strings.Contains(file, "udp") - content, err := os.ReadFile(file) + f, err := os.Open(file) if err != nil { return nil, err } - lines := strings.Split(string(content), "\n") - if len(lines) < 1 { - return nil, ErrFileParse - } + defer f.Close() - for _, line := range lines[1:] { - fields := strings.Fields(line) - if len(fields) == 0 { - continue - } + var netIPSocket NetIPSocket + isUDP := strings.Contains(file, "udp") + + lr := io.LimitReader(f, readLimit) + s := bufio.NewScanner(lr) + s.Scan() // skip first line with headers + for s.Scan() { + fields := strings.Fields(s.Text()) line, err := parseNetIPSocketLine(fields, isUDP) if err != nil { return nil, err } netIPSocket = append(netIPSocket, line) } + if err := s.Err(); err != nil { + return nil, err + } return netIPSocket, nil } // newNetIPSocketSummary creates a new NetIPSocket{,6} from the contents of the given file. func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) { - var netIPSocketSummary NetIPSocketSummary - var udpPacketDrops uint64 - isUDP := strings.Contains(file, "udp") - content, err := os.ReadFile(file) + f, err := os.Open(file) if err != nil { return nil, err } - lines := strings.Split(string(content), "\n") - if len(lines) < 1 { - return nil, ErrFileParse - } + defer f.Close() - for _, line := range lines[1:] { - fields := strings.Fields(line) - if len(fields) == 0 { - continue - } + var netIPSocketSummary NetIPSocketSummary + var udpPacketDrops uint64 + isUDP := strings.Contains(file, "udp") + + lr := io.LimitReader(f, readLimit) + s := bufio.NewScanner(lr) + s.Scan() // skip first line with headers + for s.Scan() { + fields := strings.Fields(s.Text()) line, err := parseNetIPSocketLine(fields, isUDP) if err != nil { return nil, err @@ -118,6 +129,9 @@ func newNetIPSocketSummary(file string) (*NetIPSocketSummary, error) { netIPSocketSummary.Drops = &udpPacketDrops } } + if err := s.Err(); err != nil { + return nil, err + } return &netIPSocketSummary, nil }