mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 17:50:12 +00:00
195 lines
5.1 KiB
Go
195 lines
5.1 KiB
Go
//go:build linux
|
|
|
|
package process
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"syscall"
|
|
)
|
|
|
|
func readRSSBytes(pid int) (uint64, error) {
|
|
path := filepath.Join(strconv.Itoa(pid), "status")
|
|
procRoot, err := os.OpenRoot("/proc")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer procRoot.Close()
|
|
file, err := procRoot.Open(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer file.Close()
|
|
scanner := bufio.NewScanner(file)
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) == 3 && fields[0] == "VmRSS:" && fields[2] == "kB" {
|
|
kilobytes, err := strconv.ParseUint(fields[1], 10, 64)
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parse VmRSS: %w", err)
|
|
}
|
|
return kilobytes * 1024, nil
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return 0, fmt.Errorf("read %s: %w", path, err)
|
|
}
|
|
return 0, errors.New("VmRSS not found")
|
|
}
|
|
|
|
func descendantPIDs(rootPID int) ([]int, error) {
|
|
entries, err := os.ReadDir("/proc")
|
|
if err != nil {
|
|
return nil, fmt.Errorf("read /proc: %w", err)
|
|
}
|
|
children := make(map[int][]int)
|
|
for _, entry := range entries {
|
|
pid, err := strconv.Atoi(entry.Name())
|
|
if err != nil || !entry.IsDir() {
|
|
continue
|
|
}
|
|
parentPID, err := readParentPID(pid)
|
|
if err != nil {
|
|
// /proc is a live snapshot: an unrelated process can disappear
|
|
// between ReadDir and reading stat. Root PID reads stay strict.
|
|
if os.IsNotExist(err) || errors.Is(err, syscall.ESRCH) {
|
|
continue
|
|
}
|
|
return nil, err
|
|
}
|
|
children[parentPID] = append(children[parentPID], pid)
|
|
}
|
|
descendants := make([]int, 0)
|
|
queue := append([]int(nil), children[rootPID]...)
|
|
for len(queue) > 0 {
|
|
pid := queue[0]
|
|
queue = queue[1:]
|
|
descendants = append(descendants, pid)
|
|
queue = append(queue, children[pid]...)
|
|
}
|
|
sort.Ints(descendants)
|
|
return descendants, nil
|
|
}
|
|
|
|
func readParentPID(pid int) (int, error) {
|
|
path := filepath.Join(strconv.Itoa(pid), "stat")
|
|
procRoot, err := os.OpenRoot("/proc")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer procRoot.Close()
|
|
data, err := procRoot.ReadFile(path)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
closingParenthesis := strings.LastIndexByte(string(data), ')')
|
|
if closingParenthesis < 0 {
|
|
return 0, fmt.Errorf("parse %s: missing command terminator", path)
|
|
}
|
|
fields := strings.Fields(string(data[closingParenthesis+1:]))
|
|
if len(fields) < 2 {
|
|
return 0, fmt.Errorf("parse %s: missing parent PID", path)
|
|
}
|
|
parentPID, err := strconv.Atoi(fields[1])
|
|
if err != nil {
|
|
return 0, fmt.Errorf("parse %s parent PID: %w", path, err)
|
|
}
|
|
return parentPID, nil
|
|
}
|
|
|
|
type FDObservation struct {
|
|
Number int
|
|
Target string
|
|
}
|
|
|
|
func processFDs(pid int, captureObservations bool) (int, map[uint64]struct{}, []FDObservation, error) {
|
|
directory := filepath.Join("/proc", strconv.Itoa(pid), "fd")
|
|
entries, err := os.ReadDir(directory)
|
|
if err != nil {
|
|
return 0, nil, nil, err
|
|
}
|
|
count := 0
|
|
sockets := make(map[uint64]struct{})
|
|
var observations []FDObservation
|
|
if captureObservations {
|
|
observations = make([]FDObservation, 0, len(entries))
|
|
}
|
|
for _, entry := range entries {
|
|
descriptor, err := strconv.Atoi(entry.Name())
|
|
if err != nil || descriptor < 3 {
|
|
continue
|
|
}
|
|
target, err := os.Readlink(filepath.Join(directory, entry.Name()))
|
|
if err != nil {
|
|
if os.IsNotExist(err) {
|
|
continue
|
|
}
|
|
return 0, nil, nil, err
|
|
}
|
|
count++
|
|
if captureObservations {
|
|
observations = append(observations, FDObservation{Number: descriptor, Target: target})
|
|
}
|
|
if inode, exists := parseSocketInode(target); exists {
|
|
sockets[inode] = struct{}{}
|
|
}
|
|
}
|
|
if captureObservations {
|
|
sort.Slice(observations, func(left, right int) bool {
|
|
return observations[left].Number < observations[right].Number || (observations[left].Number == observations[right].Number && observations[left].Target < observations[right].Target)
|
|
})
|
|
}
|
|
return count, sockets, observations, nil
|
|
}
|
|
|
|
func parseSocketInode(target string) (uint64, bool) {
|
|
if !strings.HasPrefix(target, "socket:[") || !strings.HasSuffix(target, "]") {
|
|
return 0, false
|
|
}
|
|
inode, err := strconv.ParseUint(strings.TrimSuffix(strings.TrimPrefix(target, "socket:["), "]"), 10, 64)
|
|
return inode, err == nil
|
|
}
|
|
|
|
func listeningSocketInodes(pid int, protocol string) (map[uint64]struct{}, error) {
|
|
if protocol != "tcp" && protocol != "tcp6" {
|
|
return nil, errors.New("unsupported proc network protocol")
|
|
}
|
|
path := filepath.Join(strconv.Itoa(pid), "net", protocol)
|
|
procRoot, err := os.OpenRoot("/proc")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer procRoot.Close()
|
|
file, err := procRoot.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer file.Close()
|
|
listeners := make(map[uint64]struct{})
|
|
scanner := bufio.NewScanner(file)
|
|
if scanner.Scan() {
|
|
// Skip the stable kernel table header.
|
|
}
|
|
for scanner.Scan() {
|
|
fields := strings.Fields(scanner.Text())
|
|
if len(fields) < 10 || fields[3] != "0A" {
|
|
continue
|
|
}
|
|
inode, err := strconv.ParseUint(fields[9], 10, 64)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("parse %s listener inode: %w", path, err)
|
|
}
|
|
listeners[inode] = struct{}{}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, fmt.Errorf("read %s: %w", path, err)
|
|
}
|
|
return listeners, nil
|
|
}
|