diff --git a/common/common_darwin.go b/common/common_darwin.go index 7d6f3c6..8d43de0 100644 --- a/common/common_darwin.go +++ b/common/common_darwin.go @@ -4,6 +4,7 @@ package common import ( "os/exec" + "strconv" "strings" "syscall" "unsafe" @@ -58,3 +59,27 @@ func CallSyscall(mib []int32) ([]byte, uint64, error) { return buf, length, nil } + +func CallLsof(invoke Invoker, pid int32, args ...string) ([]string, error) { + var cmd []string + if pid == 0 { // will get from all processes. + cmd = []string{"-a", "-n", "-P"} + } else { + cmd = []string{"-a", "-n", "-P", "-p", strconv.Itoa(int(pid))} + } + cmd = append(cmd, args...) + out, err := invoke.Command("/usr/sbin/lsof", cmd...) + if err != nil { + return []string{}, err + } + lines := strings.Split(string(out), "\n") + + var ret []string + for _, l := range lines[1:] { + if len(l) == 0 { + continue + } + ret = append(ret, l) + } + return ret, nil +} diff --git a/net/net.go b/net/net.go index 8255177..339c8c3 100644 --- a/net/net.go +++ b/net/net.go @@ -3,8 +3,16 @@ package net import ( "encoding/json" "net" + + "github.com/shirou/gopsutil/common" ) +var invoke common.Invoker + +func init() { + invoke = common.Invoke{} +} + type NetIOCountersStat struct { Name string `json:"name"` // interface name BytesSent uint64 `json:"bytes_sent"` // number of bytes sent diff --git a/net/net_darwin.go b/net/net_darwin.go index 65c21f8..618d76e 100644 --- a/net/net_darwin.go +++ b/net/net_darwin.go @@ -3,13 +3,23 @@ package net import ( + "fmt" + "net" "os/exec" "strconv" "strings" + "syscall" "github.com/shirou/gopsutil/common" ) +var constMap = map[string]int{ + "TCP": syscall.SOCK_STREAM, + "UDP": syscall.SOCK_DGRAM, + "IPv4": syscall.AF_INET, + "IPv6": syscall.AF_INET6, +} + // example of netstat -idbn output on yosemite // Name Mtu Network Address Ipkts Ierrs Ibytes Opkts Oerrs Obytes Coll Drop // lo0 16384 869107 0 169411755 869107 0 169411755 0 0 @@ -90,3 +100,129 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +// Return a list of network connections opened by a process +func NetConnections(kind string) ([]NetConnectionStat, error) { + var ret []NetConnectionStat + + args := []string{"-i"} + switch strings.ToLower(kind) { + default: + fallthrough + case "": + fallthrough + case "all": + fallthrough + case "inet": + args = append(args, "tcp") + case "inet4": + args = append(args, "4") + case "inet6": + args = append(args, "6") + case "tcp": + args = append(args, "tcp") + case "tcp4": + args = append(args, "4tcp") + case "tcp6": + args = append(args, "6tcp") + case "udp": + args = append(args, "udp") + case "udp4": + args = append(args, "6udp") + case "udp6": + args = append(args, "6udp") + case "unix": + return ret, common.NotImplementedError + } + + // we can not use -F filter to get all of required information at once. + r, err := common.CallLsof(invoke, 0, args...) + if err != nil { + return nil, err + } + for _, rr := range r { + if strings.HasPrefix(rr, "COMMAND") { + continue + } + n, err := parseNetLine(rr) + if err != nil { + // fmt.Println(err) // TODO: should debug print? + continue + } + + ret = append(ret, n) + } + + return ret, nil +} + +func parseNetLine(line string) (NetConnectionStat, error) { + f := strings.Fields(line) + if len(f) < 9 { + return NetConnectionStat{}, fmt.Errorf("wrong line,%s", line) + } + + pid, err := strconv.Atoi(f[1]) + if err != nil { + return NetConnectionStat{}, err + } + fd, err := strconv.Atoi(strings.Trim(f[3], "u")) + if err != nil { + return NetConnectionStat{}, fmt.Errorf("unknown fd, %s", f[3]) + } + netFamily, ok := constMap[f[4]] + if !ok { + return NetConnectionStat{}, fmt.Errorf("unknown family, %s", f[4]) + } + netType, ok := constMap[f[7]] + if !ok { + return NetConnectionStat{}, fmt.Errorf("unknown type, %s", f[7]) + } + + laddr, raddr, err := parseNetAddr(f[8]) + if err != nil { + return NetConnectionStat{}, fmt.Errorf("failed to parse netaddr, %s", f[8]) + } + + n := NetConnectionStat{ + Fd: uint32(fd), + Family: uint32(netFamily), + Type: uint32(netType), + Laddr: laddr, + Raddr: raddr, + Pid: int32(pid), + } + if len(f) == 10 { + n.Status = strings.Trim(f[9], "()") + } + + return n, nil +} + +func parseNetAddr(line string) (laddr Addr, raddr Addr, err error) { + parse := func(l string) (Addr, error) { + host, port, err := net.SplitHostPort(l) + if err != nil { + return Addr{}, fmt.Errorf("wrong addr, %s", l) + } + lport, err := strconv.Atoi(port) + if err != nil { + return Addr{}, err + } + return Addr{IP: host, Port: uint32(lport)}, nil + } + + addrs := strings.Split(line, "->") + if len(addrs) == 0 { + return laddr, raddr, fmt.Errorf("wrong netaddr, %s", line) + } + laddr, err = parse(addrs[0]) + if len(addrs) == 2 { // remote addr exists + raddr, err = parse(addrs[1]) + if err != nil { + return laddr, raddr, err + } + } + + return laddr, raddr, err +} diff --git a/net/net_freebsd.go b/net/net_freebsd.go index 703d321..7cc93a5 100644 --- a/net/net_freebsd.go +++ b/net/net_freebsd.go @@ -81,3 +81,9 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +func NetConnections(kind string) ([]NetConnectionStat, error) { + var ret []NetConnectionStat + + return ret, common.NotImplementedError +} diff --git a/net/net_linux.go b/net/net_linux.go index 9184439..82e25a0 100644 --- a/net/net_linux.go +++ b/net/net_linux.go @@ -89,3 +89,9 @@ func NetIOCounters(pernic bool) ([]NetIOCountersStat, error) { return ret, nil } + +func NetConnections(kind string) ([]NetConnectionStat, error) { + var ret []NetConnectionStat + + return ret, common.NotImplementedError +} diff --git a/net/net_test.go b/net/net_test.go index a6648fb..c04c0e2 100644 --- a/net/net_test.go +++ b/net/net_test.go @@ -120,3 +120,19 @@ func TestNetInterfaces(t *testing.T) { } } } + +func TestNetConnections(t *testing.T) { + v, err := NetConnections("inet") + if err != nil { + t.Errorf("could not get NetConnections: %v", err) + } + if len(v) == 0 { + t.Errorf("could not get NetConnections: %v", v) + } + for _, vv := range v { + if vv.Family == 0 { + t.Errorf("invalid NetConnections: %v", vv) + } + } + +} diff --git a/process/process_darwin.go b/process/process_darwin.go index 5b1f1bd..634d5cf 100644 --- a/process/process_darwin.go +++ b/process/process_darwin.go @@ -93,18 +93,22 @@ func (p *Process) Cwd() (string, error) { return "", common.NotImplementedError } func (p *Process) Parent() (*Process, error) { - r, err := callLsof("R", p.Pid) + rr, err := common.CallLsof(invoke, p.Pid, "-FR") if err != nil { return nil, err } - if len(r) != 1 { // TODO: pid 1 - return nil, fmt.Errorf("wrong number of parents") - } - v, err := strconv.Atoi(r[0]) - if err != nil { - return nil, err + for _, r := range rr { + if strings.HasPrefix(r, "p") { // skip if process + continue + } + l := string(r) + v, err := strconv.Atoi(strings.Replace(l, "R", "", 1)) + if err != nil { + return nil, err + } + return NewProcess(int32(v)) } - return NewProcess(int32(v)) + return nil, fmt.Errorf("could not find parent line") } func (p *Process) Status() (string, error) { r, err := callPs("state", p.Pid, false) @@ -415,26 +419,3 @@ func callPs(arg string, pid int32, threadOption bool) ([][]string, error) { return ret, nil } - -func callLsof(arg string, pid int32) ([]string, error) { - var cmd []string - if pid == 0 { // will get from all processes. - cmd = []string{"-F" + arg} - } else { - cmd = []string{"-a", "-F" + arg, "-p", strconv.Itoa(int(pid))} - } - out, err := invoke.Command("/usr/sbin/lsof", cmd...) - if err != nil { - return []string{}, err - } - lines := strings.Split(string(out), "\n") - - var ret []string - for _, l := range lines[1:] { - if strings.HasPrefix(l, arg) { - ret = append(ret, l[1:]) // delete first char - } - } - - return ret, nil -} diff --git a/process/process_test.go b/process/process_test.go index 2d2c9c5..dba0ed9 100644 --- a/process/process_test.go +++ b/process/process_test.go @@ -280,12 +280,12 @@ func Test_Parent(t *testing.T) { c, err := p.Parent() if err != nil { - t.Errorf("error %v", err) + t.Fatalf("error %v", err) } if c == nil { - t.Errorf("could not get parent") + t.Fatalf("could not get parent") } if c.Pid == 0 { - t.Errorf("wrong parent pid") + t.Fatalf("wrong parent pid") } }