@ -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 = process InetWithContext( 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 = processUnix DiagWithContext( 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 )
inode Str , err := os . Readlink ( inodePath )
if err != nil {
if err != nil {
continue
continue
}
}
if ! strings . HasPrefix ( inode , "socket:[" ) {
if ! strings . HasPrefix ( inode Str , "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 ) {
if len ( src ) != 16 {
return nil , fmt . Errorf ( "invalid IPv6 string" )
}
buf := make ( [ ] byte , 0 , 16 )
for i := 0 ; i < len ( src ) ; i += 4 {
r := ReverseWithContext ( ctx , src [ i : i + 4 ] )
buf = append ( buf , r ... )
}
return net . IP ( buf ) , nil
}
func processInet ( file string , kind netConnectionKindType , inodes map [ string ] [ ] inodeMap , filterPid int32 ) ( [ ] connTmp , error ) {
opt := & diag . NetOption {
return processInetWithContext ( context . Background ( ) , file , kind , inodes , filterPid )
Family : uint8 ( kind . family ) ,
State : ^ uint32 ( 0 ) ,
}
}
func processInetWithContext ( ctx context . Context , file string , kind netConnectionKindType , inodes map [ string ] [ ] inodeMap , filterPid int32 ) ( [ ] connTmp , error ) {
switch kind . sockType {
if strings . HasSuffix ( file , "6" ) && ! common . PathExists ( file ) {
case 1 :
// IPv6 not supported, return empty.
opt . Protocol = unix . IPPROTO_TCP
return [ ] connTmp { } , nil
case 2 :
opt . Protocol = unix . IPPROTO_UDP
default :
return nil , fmt . Errorf ( "unhandled socket type" )
}
}
// Read the contents of the /proc file with a single read sys call.
conns , err := nl . NetDump ( opt )
// 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 [ in ode]
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. F amily,
sockType : kind . sockType ,
sockType : kind . sockType ,
laddr : la ,
laddr : Addr {
raddr : ra ,
IP : src . String ( ) ,
status : status ,
Port : uint32 ( srcPort ) ,
} ,
raddr : Addr {
IP : dst . String ( ) ,
Port : uint32 ( dstPort ) ,
} ,
pid : pid ,
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.
// 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 {
return nil , err
}
lines := bytes . Split ( contents , [ ] byte ( "\n" ) )
var ret [ ] connTmp
var ret [ ] connTmp
// skip first line
for _ , line := range lines [ 1 : ] {
conns , err := nl . UnixDump ( & diag . UnixOption {
tokens := strings . Fields ( string ( line ) )
State : ^ uint32 ( 0 ) ,
if len ( tokens ) < 6 {
Show : ^ uint32 ( 0 ) ,
continue
} )
}
st , err := strconv . Atoi ( tokens [ 4 ] )
if err != nil {
if err != nil {
return nil , err
return nil , err
}
}
for _ , conn := range conns {
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. f amily,
family : conn. F amily,
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 {