Merge "netutil: On FreeBSD replace call to lsof(1) with sockstat(1)."

This commit is contained in:
Brad Fitzpatrick 2013-08-18 17:12:02 +00:00 committed by Gerrit Code Review
commit f665ca05d3
2 changed files with 151 additions and 15 deletions

View File

@ -29,6 +29,7 @@ import (
"os"
"os/exec"
"os/user"
"regexp"
"runtime"
"strconv"
"strings"
@ -84,10 +85,12 @@ func AddrPairUserid(local, remote net.Addr) (uid int, err error) {
localv4, remotev4)
}
if runtime.GOOS == "darwin" || runtime.GOOS == "freebsd" {
switch runtime.GOOS {
case "darwin":
return uidFromLsof(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port)
}
if runtime.GOOS == "linux" {
case "freebsd":
return uidFromSockstat(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port)
case "linux":
file := "/proc/net/tcp"
if !localv4 {
file = "/proc/net/tcp6"
@ -97,7 +100,7 @@ func AddrPairUserid(local, remote net.Addr) (uid int, err error) {
return -1, fmt.Errorf("Error opening %s: %v", file, err)
}
defer f.Close()
return uidFromReader(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port, f)
return uidFromProcReader(lAddr.IP, lAddr.Port, rAddr.IP, rAddr.Port, f)
}
return 0, ErrUnsupportedOS
}
@ -125,6 +128,19 @@ func (p maybeBrackets) String() string {
return s
}
// Store in a var so we can override for testing.
var uidFromUsername = func(username string) (uid int, err error) {
if uid := os.Getuid(); uid != 0 && username == os.Getenv("USER") {
return uid, nil
}
u, err := user.Lookup(username)
if err == nil {
uid, err := strconv.Atoi(u.Uid)
return uid, err
}
return 0, err
}
func uidFromLsof(lip net.IP, lport int, rip net.IP, rport int) (uid int, err error) {
seek := fmt.Sprintf("%s:%d->%s:%d", maybeBrackets(lip), lport, maybeBrackets(rip), rport)
seekb := []byte(seek)
@ -169,21 +185,52 @@ func uidFromLsof(lip net.IP, lport int, rip net.IP, rport int) (uid int, err err
continue
}
username := string(f[2])
if uid := os.Getuid(); uid != 0 && username == os.Getenv("USER") {
return uid, nil
}
u, err := user.Lookup(username)
if err == nil {
uid, err := strconv.Atoi(u.Uid)
return uid, err
}
return 0, err
return uidFromUsername(username)
}
return -1, ErrNotFound
}
func uidFromReader(lip net.IP, lport int, rip net.IP, rport int, r io.Reader) (uid int, err error) {
func uidFromSockstat(lip net.IP, lport int, rip net.IP, rport int) (int, error) {
cmd := exec.Command("sockstat", "-Ptcp")
stdout, err := cmd.StdoutPipe()
if err != nil {
return -1, err
}
defer cmd.Wait()
defer stdout.Close()
err = cmd.Start()
if err != nil {
return -1, err
}
defer cmd.Process.Kill()
return uidFromSockstatReader(lip, lport, rip, rport, stdout)
}
func uidFromSockstatReader(lip net.IP, lport int, rip net.IP, rport int, r io.Reader) (int, error) {
pat, err := regexp.Compile(fmt.Sprintf(`^([^ ]+).*%s:%d *%s:%d$`,
lip.String(), lport, rip.String(), rport))
if err != nil {
return -1, err
}
scanner := bufio.NewScanner(r)
for scanner.Scan() {
l := scanner.Text()
m := pat.FindStringSubmatch(l)
if len(m) == 2 {
return uidFromUsername(m[1])
}
}
if err := scanner.Err(); err != nil {
return -1, err
}
return -1, ErrNotFound
}
func uidFromProcReader(lip net.IP, lport int, rip net.IP, rport int, r io.Reader) (uid int, err error) {
buf := bufio.NewReader(r)
localHex := ""

View File

@ -127,13 +127,62 @@ func TestHTTPAuth(t *testing.T) {
}
}
func testUidFromUsername(username string) (int, error) {
switch username {
case "really-long-user":
return 1000, nil
case "root":
return 0, nil
}
panic("Unhandled username specified in test")
}
func TestParseFreeBSDSockstat(t *testing.T) {
uidFromUsername = testUidFromUsername
pairs := []struct {
uid int
lip, rip net.IP
lport, rport int
}{
{
// "really-long-user"
uid: 1000,
lip: net.ParseIP("192.168.123.5"), lport: 8000,
rip: net.ParseIP("192.168.123.21"), rport: 49826,
},
{
// "really-long-user"
uid: 1000,
lip: net.ParseIP("192.168.123.5"), lport: 9000,
rip: net.ParseIP("192.168.123.21"), rport: 49866,
},
{
// "root"
uid: 0,
lip: net.ParseIP("192.168.123.5"), lport: 22,
rip: net.ParseIP("192.168.123.21"), rport: 49747,
},
}
for _, p := range pairs {
uid, err := uidFromSockstatReader(p.lip, p.lport, p.rip, p.rport, strings.NewReader(sockstatPtcp))
if err != nil {
t.Error(err)
}
if p.uid != uid {
t.Error("Got", uid, "want", p.uid)
}
}
}
func TestParseLinuxTCPStat4(t *testing.T) {
lip, lport := net.ParseIP("67.218.110.129"), 43436
rip, rport := net.ParseIP("207.7.148.195"), 80
// 816EDA43:A9AC C39407CF:0050
// 43436 80
uid, err := uidFromReader(lip, lport, rip, rport, strings.NewReader(tcpstat4))
uid, err := uidFromProcReader(lip, lport, rip, rport, strings.NewReader(tcpstat4))
if err != nil {
t.Error(err)
}
@ -157,3 +206,43 @@ var tcpstat4 = ` sl local_address rem_address st tx_queue rx_queue tr tm->wh
12: 816EDA43:AFEF 51357D4A:01BB 01 00000000:00000000 02:00000685 00000000 61652 0 8752937 2 ffff880136375480 87 4 2 4 -1
13: 0100007F:D981 0100007F:C204 01 00000000:00000000 00:00000000 00000000 61652 0 8722933 1 ffff880036b30d00 21 4 0 3 -1
`
// Output of 'sockstat -Ptcp'. User 'really-long-user' running two instances
// of nc copied to 'really-only-process-name' and 'spc in name' run with -l
// 8000 and -l 9000 respectively. Two connections were then open from
// 192.167.123.21 using 'nc 192.168.123.5 8000' and 'nc 192.168.123.5 9000'.
var sockstatPtcp = `
sockstat -Ptcp
USER COMMAND PID FD PROTO LOCAL ADDRESS FOREIGN ADDRESS
really-long-user spc in nam63210 3 tcp4 *:9000 *:*
really-long-user spc in nam63210 4 tcp4 192.168.123.5:9000192.168.123.21:49866
www nginx 62982 7 tcp4 *:80 *:*
www nginx 62982 8 tcp6 *:80 *:*
really-long-user really-lon62928 3 tcp4 *:8000 *:*
really-long-user really-lon62928 4 tcp4 192.168.123.5:8000192.168.123.21:49826
root sshd 62849 5 tcp4 192.168.123.5:22 192.168.123.21:49819
root sshd 61819 5 tcp4 192.168.123.5:22 192.168.123.21:49747
camlistore sshd 61746 5 tcp4 192.168.123.5:22 192.168.123.21:49739
root sshd 61744 5 tcp4 192.168.123.5:22 192.168.123.21:49739
camlistore camlistore10941 7 tcp4 6 *:3179 *:*
camlistore sshd 91620 5 tcp4 192.168.123.5:22 192.168.123.2:13404
root sshd 91618 5 tcp4 192.168.123.5:22 192.168.123.2:13404
root sshd 2309 4 tcp6 *:22 *:*
root sshd 2309 5 tcp4 *:22 *:*
root nginx 2152 7 tcp4 *:80 *:*
root nginx 2152 8 tcp6 *:80 *:*
root python2.7 2076 3 tcp4 127.0.0.1:9042 *:*
root python2.7 2076 6 tcp4 127.0.0.1:9042 127.0.0.1:51930
root python2.7 2076 7 tcp4 127.0.0.1:9042 127.0.0.1:20433
root python2.7 2076 8 tcp4 127.0.0.1:9042 127.0.0.1:55807
root rpc.statd 1630 5 tcp6 *:664 *:*
root rpc.statd 1630 7 tcp4 *:664 *:*
root nfsd 1618 5 tcp4 *:2049 *:*
root nfsd 1618 6 tcp6 *:2049 *:*
root mountd 1604 6 tcp6 *:792 *:*
root mountd 1604 8 tcp4 *:792 *:*
root rpcbind 1600 8 tcp6 *:111 *:*
root rpcbind 1600 11 tcp4 *:111 *:*
? ? ? ? tcp4 *:895 *:*
? ? ? ? tcp6 *:777 *:*
`