diff --git a/internal/common/common.go b/internal/common/common.go index 8c38bdb..53e0667 100644 --- a/internal/common/common.go +++ b/internal/common/common.go @@ -206,6 +206,16 @@ func StringsContains(target []string, src string) bool { return false } +// IntContains checks the src in any int of the target int slice. +func IntContains(target []int, src int) bool { + for _, t := range target { + if src == t { + return true + } + } + return false +} + // get struct attributes. // This method is used only for debugging platform dependent code. func attributes(m interface{}) map[string]reflect.Type { diff --git a/net/net_linux.go b/net/net_linux.go index e2e217d..097da45 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -3,9 +3,15 @@ package net import ( + "encoding/hex" "errors" + "fmt" + "io/ioutil" + "net" + "os" "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/internal/common" ) @@ -192,3 +198,291 @@ func NetFilterCounters() ([]NetFilterStat, error) { stats = append(stats, payload) return stats, nil } + +type netConnectionKindType struct { + family int + sockType int +} + +var KindTCP4 = netConnectionKindType{ + family: syscall.AF_INET, + sockType: syscall.SOCK_STREAM, +} +var KindTCP6 = netConnectionKindType{ + family: syscall.AF_INET6, + sockType: syscall.SOCK_STREAM, +} +var KindUDP4 = netConnectionKindType{ + family: syscall.AF_INET, + sockType: syscall.SOCK_DGRAM, +} +var KindUDP6 = netConnectionKindType{ + family: syscall.AF_INET6, + sockType: syscall.SOCK_DGRAM, +} +var KindUNIX = netConnectionKindType{ + family: syscall.AF_UNIX, +} + +var netConnectionKindMap = map[string][]netConnectionKindType{ + "all": []netConnectionKindType{KindTCP4, KindTCP6, KindUDP4, KindUDP6, KindUNIX}, + "tcp": []netConnectionKindType{KindTCP4, KindTCP6}, + "tcp4": []netConnectionKindType{KindTCP4}, + "tcp6": []netConnectionKindType{KindTCP6}, + "udp": []netConnectionKindType{KindUDP4, KindUDP6}, + "udp4": []netConnectionKindType{KindUDP4}, + "udp6": []netConnectionKindType{KindUDP6}, + "unix": []netConnectionKindType{KindUNIX}, + "inet": []netConnectionKindType{KindTCP4, KindTCP6, KindUDP4, KindUDP6}, + "inet4": []netConnectionKindType{KindTCP4, KindUDP4}, + "inet6": []netConnectionKindType{KindTCP6, KindUDP6}, +} + +type inodeMap struct { + pid int32 + fd string +} + +type bb struct { + fd int + family int + sockType int + laddr string + raddr string + status string + bound_pid int32 +} + +// Return a list of network connections opened. +func NetConnections(kind string) ([]NetConnectionStat, error) { + return NetConnectionsPid(kind, 0) +} + +// Return a list of network connections opened by a process. +func NetConnectionsPid(kind string, pid int32) ([]NetConnectionStat, error) { + tmap, ok := netConnectionKindMap[kind] + if !ok { + return nil, fmt.Errorf("invalid kind, %s", kind) + } + root := common.HostProc() + var err error + var inodes map[string][]inodeMap + if pid == 0 { + inodes, err = getProcInodesAll(root) + } else { + inodes, err = getProcInodes(root, pid) + } + if err != nil { + return nil, fmt.Errorf("cound not get pid(s), %d", pid) + } + + for _, t := range tmap { + fmt.Println(t) + fmt.Println(inodes) + + var path string + var ls []string + switch t.family { + case syscall.AF_INET: + path = fmt.Sprintf("%d/net/%s", pid, "tcp") + ls, err = processInet(path, t, inodes, pid) + case syscall.AF_INET6: + path = fmt.Sprintf("%d/net/%s", pid, "tcp6") + ls, err = processInet(path, t, inodes, pid) + case syscall.AF_UNIX: + path = fmt.Sprintf("%d/net/%s", pid, "unix") + ls, err = processInet(path, t, inodes, pid) + } + if err != nil { + return nil, err + } + for _, sss := range ls { + fmt.Println(sss) + } + + } + + return []NetConnectionStat{}, nil +} + +// getProcInodes returnes fd of the pid. +func getProcInodes(root string, pid int32) (map[string][]inodeMap, error) { + ret := make(map[string][]inodeMap) + + dir := fmt.Sprintf("%s/%d/fd", root, pid) + + files, err := ioutil.ReadDir(dir) + if err != nil { + return ret, nil + } + for _, fd := range files { + inodePath := fmt.Sprintf("%s/%d/fd/%s", root, pid, fd.Name()) + + inode, err := os.Readlink(inodePath) + if err != nil { + continue + } + + fmt.Println(inodePath) + if strings.HasPrefix(inode, "socket:[") { + // the process is using a socket + l := len(inode) + inode = inode[8 : l-1] + } + + _, ok := ret[inode] + if !ok { + ret[inode] = make([]inodeMap, 0) + } + + i := inodeMap{ + pid: pid, + fd: fd.Name(), + } + ret[inode] = append(ret[inode], i) + } + + return ret, nil +} + +// Pids retunres all pids. +// Note: this is a copy of process_linux.Pids() +// FIXME: Import process occures import cycle. +// move to common made other platform breaking. Need consider. +func Pids() ([]int32, error) { + var ret []int32 + + d, err := os.Open(common.HostProc()) + if err != nil { + return nil, err + } + defer d.Close() + + fnames, err := d.Readdirnames(-1) + if err != nil { + return nil, err + } + for _, fname := range fnames { + pid, err := strconv.ParseInt(fname, 10, 32) + if err != nil { + // if not numeric name, just skip + continue + } + ret = append(ret, int32(pid)) + } + + return ret, nil +} + +func getProcInodesAll(root string) (map[string][]inodeMap, error) { + pids, err := Pids() + if err != nil { + return nil, err + } + ret := make(map[string][]inodeMap) + + for _, pid := range pids { + t, err := getProcInodes(root, pid) + if err != nil { + return ret, err + } + if len(t) == 0 { + continue + } + // TODO: update ret. + + fmt.Println(t) + } + + 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 int, src string) (net.IP, int, error) { + t := strings.Split(src, ":") + if len(t) != 2 { + return nil, 0, fmt.Errorf("does not contain port, %s", src) + } + addr := t[0] + port, err := strconv.ParseInt("0x"+t[1], 0, 64) + if err != nil { + return nil, 0, fmt.Errorf("invalid port, %s", src) + } + decoded, err := hex.DecodeString(addr) + if err != nil { + return nil, 0, fmt.Errorf("decode error:", err) + } + var ip net.IP + // Assumes this is little_endian + if family == syscall.AF_INET { + ip = net.IP(Reverse(decoded)) + } else { // IPv6 + ip, err = parseIPv6HexString(decoded) + if err != nil { + return nil, 0, err + } + } + return ip, int(port), nil +} + +// Reverse reverses array of bytes. +func Reverse(s []byte) []byte { + for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 { + s[i], s[j] = s[j], s[i] + } + return s +} + +// parseIPv6HexString parse array of bytes to IPv6 string +func parseIPv6HexString(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 := Reverse(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) ([]string, error) { + + if strings.HasSuffix(file, "6") && !common.PathExists(file) { + // IPv6 not supported, return empty. + return []string{}, nil + } + lines, err := common.ReadLines(file) + if err != nil { + return nil, err + } + // skip first line + for _, line := range lines[1:] { + l := strings.Fields(line) + if len(l) < 10 { + continue + } + laddr := l[1] + raddr := l[2] + status := l[3] + inode, err := strconv.Atoi(l[9]) + if err != nil { + continue + } + fmt.Println(laddr) + fmt.Println(raddr) + fmt.Println(status) + fmt.Println(inode) + } + + return nil, nil +} + +func processUnix(file string, kind netConnectionKindType, inodes map[string][]inodeMap, filterPid int32) ([]string, error) { + + return nil, nil +} diff --git a/net/net_linux_test.go b/net/net_linux_test.go new file mode 100644 index 0000000..debe166 --- /dev/null +++ b/net/net_linux_test.go @@ -0,0 +1,71 @@ +package net + +import ( + "fmt" + "syscall" + "testing" + + "github.com/shirou/gopsutil/internal/common" + "github.com/stretchr/testify/assert" +) + +func TestGetProcInodes(t *testing.T) { + root := common.HostProc("") + + // /proc/19957/fd + + v, err := getProcInodes(root, 19957) + if err != nil { + t.Fatal(err) + } + fmt.Println(v) +} + +type AddrTest struct { + IP string + Port int + Error bool +} + +func TestDecodeAddress(t *testing.T) { + assert := assert.New(t) + + addr := map[string]AddrTest{ + "0500000A:0016": AddrTest{ + IP: "10.0.0.5", + Port: 22, + }, + "0100007F:D1C2": AddrTest{ + IP: "127.0.0.1", + Port: 53698, + }, + "11111:0035": AddrTest{ + Error: true, + }, + "0100007F:BLAH": AddrTest{ + Error: true, + }, + "0085002452100113070057A13F025401:0035": AddrTest{ + IP: "2400:8500:1301:1052:a157:7:154:23f", + Port: 53, + }, + "00855210011307F025401:0035": AddrTest{ + Error: true, + }, + } + + for src, dst := range addr { + family := syscall.AF_INET + if len(src) > 13 { + family = syscall.AF_INET6 + } + ip, port, err := decodeAddress(family, src) + if dst.Error { + assert.NotNil(err, src) + } else { + assert.Nil(err, src) + assert.Equal(dst.IP, ip.String(), src) + assert.Equal(dst.Port, port, src) + } + } +} diff --git a/net/net_unix.go b/net/net_unix.go index fe2f1eb..c267c25 100644 --- a/net/net_unix.go +++ b/net/net_unix.go @@ -1,4 +1,4 @@ -// +build linux freebsd darwin +// +build freebsd darwin package net