net/linux: use NETLINK_SOCK_DIAG

To retrieve network information use NETLINK_SOCK_DIAG instead of walking
/proc/<PID> and parsing files.
Consequently this reduces the number of syscalls, as walking /proc/<PID> and
opening, reading and closing files is no longer required. But it also reduces
the memory footprint as reading files into memory for processing is no longer
required.

Related issues:
- https://github.com/shirou/gopsutil/issues/695
- https://github.com/shirou/gopsutil/issues/784

Supersedes https://github.com/shirou/gopsutil/pull/809

Signed-off-by: Florian Lehner <dev@der-flo.net>
pull/1660/head
Florian Lehner 12 months ago
parent 184fb1ccbc
commit 15f5f5aee3

@ -3,27 +3,25 @@ module github.com/shirou/gopsutil/v4
go 1.18 go 1.18
require ( require (
github.com/florianl/go-diag v0.0.2
github.com/google/go-cmp v0.6.0 github.com/google/go-cmp v0.6.0
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0
github.com/mdlayher/socket v0.5.1 // indirect
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c
github.com/shoenig/go-m1cpu v0.1.6 github.com/shoenig/go-m1cpu v0.1.6
github.com/stretchr/testify v1.9.0 github.com/stretchr/testify v1.9.0
github.com/tklauser/go-sysconf v0.3.12 github.com/tklauser/go-sysconf v0.3.12
github.com/yusufpapurcu/wmi v1.2.4 github.com/yusufpapurcu/wmi v1.2.4
golang.org/x/sys v0.20.0 golang.org/x/net v0.25.0 // indirect
) golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.20.1-0.20240506173926-6dfb94eaa3bd
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
require ( require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect github.com/go-ole/go-ole v1.2.6 // indirect
github.com/josharian/native v1.1.0 // indirect
github.com/mdlayher/netlink v1.7.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect github.com/tklauser/numcpus v0.6.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect

@ -1,12 +1,22 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/florianl/go-diag v0.0.1 h1:NjjG/z0eQ7J6+KrIv3hv5weH6Lxq+KBr45HPc/qdRQo=
github.com/florianl/go-diag v0.0.1/go.mod h1:Rkjs8DWYe7g4wuw4BQbYXBuUU7vaf1s2ym8N+QpCZt0=
github.com/florianl/go-diag v0.0.2 h1:4zqTkVM4egFpsEbWVh9ZAam+maGEjPIuzEAcXXsSSl4=
github.com/florianl/go-diag v0.0.2/go.mod h1:Rkjs8DWYe7g4wuw4BQbYXBuUU7vaf1s2ym8N+QpCZt0=
github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/josharian/native v1.1.0 h1:uuaP0hAbW7Y4l0ZRQ6C9zfb7Mg1mbFKry/xzDAfmtLA=
github.com/josharian/native v1.1.0/go.mod h1:7X/raswPFr05uY3HiLlYeyQntB6OO7E/d2Cu7qoaN2w=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
github.com/mdlayher/netlink v1.7.2 h1:/UtM3ofJap7Vl4QWCPDGXY8d3GIY2UGSDbK+QWmY8/g=
github.com/mdlayher/netlink v1.7.2/go.mod h1:xraEF7uJbxLhc5fpHL4cPe221LI2bdttWlU+ZGLfQSw=
github.com/mdlayher/socket v0.5.1 h1:VZaqt6RkGkt2OE9l3GcC6nZkqD3xKeQLyfleW/uBcos=
github.com/mdlayher/socket v0.5.1/go.mod h1:TjPLHI1UgwEv5J1B5q0zTZq12A/6H7nKmtTanQE37IQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
@ -22,12 +32,16 @@ github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+F
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY= github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= golang.org/x/sys v0.20.1-0.20240506173926-6dfb94eaa3bd h1:NY5cSUUSJ8nWcXOjwj7e1D9//oy6+uKCq32Dnt/p9EQ=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.1-0.20240506173926-6dfb94eaa3bd/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=

@ -4,19 +4,18 @@
package net package net
import ( import (
"bytes"
"context" "context"
"encoding/hex"
"errors" "errors"
"fmt" "fmt"
"io" "io"
"net"
"os" "os"
"strconv" "strconv"
"strings" "strings"
"syscall" "syscall"
"github.com/florianl/go-diag"
"github.com/shirou/gopsutil/v4/internal/common" "github.com/shirou/gopsutil/v4/internal/common"
"golang.org/x/sys/unix"
) )
const ( // Conntrack Column numbers const ( // Conntrack Column numbers
@ -305,24 +304,24 @@ func conntrackStatsFromFile(filename string, percpu bool) ([]ConntrackStat, erro
} }
// http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h // http://students.mimuw.edu.pl/lxr/source/include/net/tcp_states.h
var tcpStatuses = map[string]string{ var tcpStatuses = map[uint8]string{
"01": "ESTABLISHED", 1: "ESTABLISHED",
"02": "SYN_SENT", 2: "SYN_SENT",
"03": "SYN_RECV", 3: "SYN_RECV",
"04": "FIN_WAIT1", 4: "FIN_WAIT1",
"05": "FIN_WAIT2", 5: "FIN_WAIT2",
"06": "TIME_WAIT", 6: "TIME_WAIT",
"07": "CLOSE", 7: "CLOSE",
"08": "CLOSE_WAIT", 8: "CLOSE_WAIT",
"09": "LAST_ACK", 9: "LAST_ACK",
"0A": "LISTEN", 10: "LISTEN",
"0B": "CLOSING", 11: "CLOSING",
} }
type netConnectionKindType struct { type netConnectionKindType struct {
filename string
family uint32 family uint32
sockType uint32 sockType uint32
filename string
} }
var kindTCP4 = netConnectionKindType{ var kindTCP4 = netConnectionKindType{
@ -374,15 +373,15 @@ type inodeMap struct {
} }
type connTmp struct { type connTmp struct {
fd uint32 status string
family uint32 path string
sockType uint32
laddr Addr laddr Addr
raddr Addr raddr Addr
status string fd uint32
sockType uint32
pid int32 pid int32
boundPid int32 boundPid int32
path string family uint8
} }
// Return a list of network connections opened. // Return a list of network connections opened.
@ -460,7 +459,7 @@ func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, p
} }
root := common.HostProcWithContext(ctx) root := common.HostProcWithContext(ctx)
var err error var err error
var inodes map[string][]inodeMap var inodes map[uint32][]inodeMap
if pid == 0 { if pid == 0 {
inodes, err = getProcInodesAllWithContext(ctx, root, max) inodes, err = getProcInodesAllWithContext(ctx, root, max)
} else { } else {
@ -476,33 +475,31 @@ func connectionsPidMaxWithoutUidsWithContext(ctx context.Context, kind string, p
return statsFromInodesWithContext(ctx, root, pid, tmap, inodes, skipUids) return statsFromInodesWithContext(ctx, root, pid, tmap, inodes, skipUids)
} }
func statsFromInodes(root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) { func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tmap []netConnectionKindType, inodes map[uint32][]inodeMap, skipUids bool) ([]ConnectionStat, error) {
return statsFromInodesWithContext(context.Background(), root, pid, tmap, inodes, skipUids)
}
func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tmap []netConnectionKindType, inodes map[string][]inodeMap, skipUids bool) ([]ConnectionStat, error) {
dupCheckMap := make(map[string]struct{}) dupCheckMap := make(map[string]struct{})
var ret []ConnectionStat var ret []ConnectionStat
var err error // open a netlink socket
nl, err := diag.Open(&diag.Config{})
if err != nil {
return nil, err
}
defer nl.Close()
for _, t := range tmap { for _, t := range tmap {
var path string
var connKey string var connKey string
var ls []connTmp var ls []connTmp
if pid == 0 {
path = fmt.Sprintf("%s/net/%s", root, t.filename)
} else {
path = fmt.Sprintf("%s/%d/net/%s", root, pid, t.filename)
}
switch t.family { switch t.family {
case syscall.AF_INET, syscall.AF_INET6: case syscall.AF_INET, syscall.AF_INET6:
ls, err = processInetWithContext(ctx, path, t, inodes, pid) ls, err = processNetDiagWithContext(nl, t, inodes, pid)
case syscall.AF_UNIX: case syscall.AF_UNIX:
ls, err = processUnix(path, t, inodes, pid) ls, err = processUnixDiagWithContext(nl, inodes, pid)
} }
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, c := range ls { for _, c := range ls {
// Build TCP key to id the connection uniquely // Build TCP key to id the connection uniquely
// socket type, src ip, src port, dst ip, dst port and state should be enough // socket type, src ip, src port, dst ip, dst port and state should be enough
@ -514,8 +511,8 @@ func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tma
conn := ConnectionStat{ conn := ConnectionStat{
Fd: c.fd, Fd: c.fd,
Family: c.family, Family: uint32(c.family),
Type: c.sockType, Type: uint32(c.sockType),
Laddr: c.laddr, Laddr: c.laddr,
Raddr: c.raddr, Raddr: c.raddr,
Status: c.status, Status: c.status,
@ -543,8 +540,8 @@ func statsFromInodesWithContext(ctx context.Context, root string, pid int32, tma
} }
// getProcInodes returns fd of the pid. // getProcInodes returns fd of the pid.
func getProcInodes(root string, pid int32, max int) (map[string][]inodeMap, error) { func getProcInodes(root string, pid int32, max int) (map[uint32][]inodeMap, error) {
ret := make(map[string][]inodeMap) ret := make(map[uint32][]inodeMap)
dir := fmt.Sprintf("%s/%d/fd", root, pid) dir := fmt.Sprintf("%s/%d/fd", root, pid)
f, err := os.Open(dir) f, err := os.Open(dir)
@ -559,16 +556,21 @@ func getProcInodes(root string, pid int32, max int) (map[string][]inodeMap, erro
for _, dirEntry := range dirEntries { for _, dirEntry := range dirEntries {
inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, dirEntry.Name()) inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, dirEntry.Name())
inode, err := os.Readlink(inodePath) inodeStr, err := os.Readlink(inodePath)
if err != nil { if err != nil {
continue continue
} }
if !strings.HasPrefix(inode, "socket:[") { if !strings.HasPrefix(inodeStr, "socket:[") {
continue continue
} }
// the process is using a socket // the process is using a socket
l := len(inode) l := len(inodeStr)
inode = inode[8 : l-1] inodeStr = inodeStr[8 : l-1]
tmp, err := strconv.Atoi(inodeStr)
if err != nil {
return ret, err
}
inode := uint32(tmp)
_, ok := ret[inode] _, ok := ret[inode]
if !ok { if !ok {
ret[inode] = make([]inodeMap, 0) ret[inode] = make([]inodeMap, 0)
@ -625,8 +627,8 @@ func PidsWithContext(ctx context.Context) ([]int32, error) {
// FIXME: Import process occures import cycle. // FIXME: Import process occures import cycle.
// see remarks on pids() // see remarks on pids()
type process struct { type process struct {
Pid int32 `json:"pid"`
uids []int32 uids []int32
Pid int32 `json:"pid"`
} }
// Uids returns user ids of the process as a slice of the int // Uids returns user ids of the process as a slice of the int
@ -668,16 +670,16 @@ func (p *process) fillFromStatus(ctx context.Context) error {
return nil return nil
} }
func getProcInodesAll(root string, max int) (map[string][]inodeMap, error) { func getProcInodesAll(root string, max int) (map[uint32][]inodeMap, error) {
return getProcInodesAllWithContext(context.Background(), root, max) return getProcInodesAllWithContext(context.Background(), root, max)
} }
func getProcInodesAllWithContext(ctx context.Context, root string, max int) (map[string][]inodeMap, error) { func getProcInodesAllWithContext(ctx context.Context, root string, max int) (map[uint32][]inodeMap, error) {
pids, err := PidsWithContext(ctx) pids, err := PidsWithContext(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
ret := make(map[string][]inodeMap) ret := make(map[uint32][]inodeMap)
for _, pid := range pids { for _, pid := range pids {
t, err := getProcInodes(root, pid, max) t, err := getProcInodes(root, pid, max)
@ -697,48 +699,6 @@ func getProcInodesAllWithContext(ctx context.Context, root string, max int) (map
return ret, nil return ret, nil
} }
// decodeAddress decode addresse represents addr in proc/net/*
// ex:
// "0500000A:0016" -> "10.0.0.5", 22
// "0085002452100113070057A13F025401:0035" -> "2400:8500:1301:1052:a157:7:154:23f", 53
func decodeAddress(family uint32, src string) (Addr, error) {
return decodeAddressWithContext(context.Background(), family, src)
}
func decodeAddressWithContext(ctx context.Context, family uint32, src string) (Addr, error) {
t := strings.Split(src, ":")
if len(t) != 2 {
return Addr{}, fmt.Errorf("does not contain port, %s", src)
}
addr := t[0]
port, err := strconv.ParseUint(t[1], 16, 16)
if err != nil {
return Addr{}, fmt.Errorf("invalid port, %s", src)
}
decoded, err := hex.DecodeString(addr)
if err != nil {
return Addr{}, fmt.Errorf("decode error, %w", err)
}
var ip net.IP
if family == syscall.AF_INET {
if common.IsLittleEndian() {
ip = net.IP(ReverseWithContext(ctx, decoded))
} else {
ip = net.IP(decoded)
}
} else { // IPv6
ip, err = parseIPv6HexStringWithContext(ctx, decoded)
if err != nil {
return Addr{}, err
}
}
return Addr{
IP: ip.String(),
Port: uint32(port),
}, nil
}
// Reverse reverses array of bytes. // Reverse reverses array of bytes.
func Reverse(s []byte) []byte { func Reverse(s []byte) []byte {
return ReverseWithContext(context.Background(), s) return ReverseWithContext(context.Background(), s)
@ -751,59 +711,32 @@ func ReverseWithContext(ctx context.Context, s []byte) []byte {
return s return s
} }
// parseIPv6HexString parse array of bytes to IPv6 string func processNetDiagWithContext(nl *diag.Diag, kind netConnectionKindType, inodes map[uint32][]inodeMap, filterPid int32) ([]connTmp, error) {
func parseIPv6HexString(src []byte) (net.IP, error) { var ret []connTmp
return parseIPv6HexStringWithContext(context.Background(), src)
}
func parseIPv6HexStringWithContext(ctx context.Context, src []byte) (net.IP, error) { opt := &diag.NetOption{
if len(src) != 16 { Family: uint8(kind.family),
return nil, fmt.Errorf("invalid IPv6 string") State: ^uint32(0),
} }
buf := make([]byte, 0, 16) switch kind.sockType {
for i := 0; i < len(src); i += 4 { case 1:
r := ReverseWithContext(ctx, src[i:i+4]) opt.Protocol = unix.IPPROTO_TCP
buf = append(buf, r...) case 2:
opt.Protocol = unix.IPPROTO_UDP
default:
return nil, fmt.Errorf("unhandled socket type")
} }
return net.IP(buf), nil
}
func processInet(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) { conns, err := nl.NetDump(opt)
return processInetWithContext(context.Background(), file, kind, inodes, filterPid)
}
func processInetWithContext(ctx context.Context, file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) {
if strings.HasSuffix(file, "6") && !common.PathExists(file) {
// IPv6 not supported, return empty.
return []connTmp{}, nil
}
// Read the contents of the /proc file with a single read sys call.
// This minimizes duplicates in the returned connections
// For more info:
// https://github.com/shirou/gopsutil/pull/361
contents, err := os.ReadFile(file)
if err != nil { if err != nil {
return nil, err return nil, err
} }
lines := bytes.Split(contents, []byte("\n")) for _, conn := range conns {
var ret []connTmp
// skip first line
for _, line := range lines[1:] {
l := strings.Fields(string(line))
if len(l) < 10 {
continue
}
laddr := l[1]
raddr := l[2]
status := l[3]
inode := l[9]
pid := int32(0) pid := int32(0)
fd := uint32(0) fd := uint32(0)
i, exists := inodes[inode] i, exists := inodes[conn.INode]
if exists { if exists {
pid = i[0].pid pid = i[0].pid
fd = i[0].fd fd = i[0].fd
@ -811,62 +744,56 @@ func processInetWithContext(ctx context.Context, file string, kind netConnection
if filterPid > 0 && filterPid != pid { if filterPid > 0 && filterPid != pid {
continue continue
} }
if kind.sockType == syscall.SOCK_STREAM {
status = tcpStatuses[status] src, err := diag.ToNetipAddrWithFamily(conn.DiagMsg.Family, conn.ID.Src)
} else {
status = "NONE"
}
la, err := decodeAddressWithContext(ctx, kind.family, laddr)
if err != nil { if err != nil {
continue continue
} }
ra, err := decodeAddressWithContext(ctx, kind.family, raddr) srcPort := diag.Ntohs(conn.ID.SPort)
dst, err := diag.ToNetipAddrWithFamily(conn.DiagMsg.Family, conn.ID.Dst)
if err != nil { if err != nil {
continue continue
} }
dstPort := diag.Ntohs(conn.ID.DPort)
status := "NONE"
if kind.sockType == syscall.SOCK_STREAM {
status = tcpStatuses[conn.State]
}
ret = append(ret, connTmp{ ret = append(ret, connTmp{
fd: fd, fd: fd,
family: kind.family, family: conn.Family,
sockType: kind.sockType, sockType: kind.sockType,
laddr: la, laddr: Addr{
raddr: ra, IP: src.String(),
status: status, Port: uint32(srcPort),
pid: pid, },
raddr: Addr{
IP: dst.String(),
Port: uint32(dstPort),
},
pid: pid,
status: status,
}) })
} }
return ret, nil return ret, nil
} }
func processUnix(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]connTmp, error) { func processUnixDiagWithContext(nl *diag.Diag, inodes map[uint32][]inodeMap, filterPid int32) ([]connTmp, error) {
// Read the contents of the /proc file with a single read sys call. var ret []connTmp
// This minimizes duplicates in the returned connections
// For more info: conns, err := nl.UnixDump(&diag.UnixOption{
// https://github.com/shirou/gopsutil/pull/361 State: ^uint32(0),
contents, err := os.ReadFile(file) Show: ^uint32(0),
})
if err != nil { if err != nil {
return nil, err return nil, err
} }
for _, conn := range conns {
lines := bytes.Split(contents, []byte("\n"))
var ret []connTmp
// skip first line
for _, line := range lines[1:] {
tokens := strings.Fields(string(line))
if len(tokens) < 6 {
continue
}
st, err := strconv.Atoi(tokens[4])
if err != nil {
return nil, err
}
inode := tokens[6]
var pairs []inodeMap var pairs []inodeMap
pairs, exists := inodes[inode] pairs, exists := inodes[conn.Ino]
if !exists { if !exists {
pairs = []inodeMap{ pairs = []inodeMap{
{}, {},
@ -877,13 +804,13 @@ func processUnix(file string, kind netConnectionKindType, inodes map[string][]in
continue continue
} }
var path string var path string
if len(tokens) == 8 { if conn.Name != nil {
path = tokens[len(tokens)-1] path = *conn.Name
} }
ret = append(ret, connTmp{ ret = append(ret, connTmp{
fd: pair.fd, fd: pair.fd,
family: kind.family, family: conn.Family,
sockType: uint32(st), sockType: uint32(conn.Type),
laddr: Addr{ laddr: Addr{
IP: path, IP: path,
}, },
@ -897,7 +824,7 @@ func processUnix(file string, kind netConnectionKindType, inodes map[string][]in
return ret, nil return ret, nil
} }
func updateMap(src map[string][]inodeMap, add map[string][]inodeMap) map[string][]inodeMap { func updateMap(src map[uint32][]inodeMap, add map[uint32][]inodeMap) map[uint32][]inodeMap {
for key, value := range add { for key, value := range add {
a, exists := src[key] a, exists := src[key]
if !exists { if !exists {

@ -7,7 +7,6 @@ import (
"net" "net"
"os" "os"
"strings" "strings"
"syscall"
"testing" "testing"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -129,66 +128,6 @@ func TestConnectionsMax(t *testing.T) {
} }
} }
type AddrTest struct {
IP string
Port int
Error bool
}
func TestDecodeAddress(t *testing.T) {
assert := assert.New(t)
addr := map[string]AddrTest{
"11111:0035": {
Error: true,
},
"0100007F:BLAH": {
Error: true,
},
"0085002452100113070057A13F025401:0035": {
IP: "2400:8500:1301:1052:a157:7:154:23f",
Port: 53,
},
"00855210011307F025401:0035": {
Error: true,
},
}
if common.IsLittleEndian() {
addr["0500000A:0016"] = AddrTest{
IP: "10.0.0.5",
Port: 22,
}
addr["0100007F:D1C2"] = AddrTest{
IP: "127.0.0.1",
Port: 53698,
}
} else {
addr["0A000005:0016"] = AddrTest{
IP: "10.0.0.5",
Port: 22,
}
addr["7F000001:D1C2"] = AddrTest{
IP: "127.0.0.1",
Port: 53698,
}
}
for src, dst := range addr {
family := syscall.AF_INET
if len(src) > 13 {
family = syscall.AF_INET6
}
addr, err := decodeAddress(uint32(family), src)
if dst.Error {
assert.NotNil(err, src)
} else {
assert.Nil(err, src)
assert.Equal(dst.IP, addr.IP, src)
assert.Equal(dst.Port, int(addr.Port), src)
}
}
}
func TestReverse(t *testing.T) { func TestReverse(t *testing.T) {
src := []byte{0x01, 0x02, 0x03} src := []byte{0x01, 0x02, 0x03}
assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src)) assert.Equal(t, []byte{0x03, 0x02, 0x01}, Reverse(src))

Loading…
Cancel
Save