mirror of https://github.com/subgraph/fw-daemon
parent
6a7db8ba8c
commit
bcbed86e37
@ -1,200 +0,0 @@
|
|||||||
package proc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/hex"
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"github.com/subgraph/fw-daemon/Godeps/_workspace/src/github.com/op/go-logging"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
var log = logging.MustGetLogger("proc")
|
|
||||||
|
|
||||||
func SetLogger(logger *logging.Logger) {
|
|
||||||
log = logger
|
|
||||||
}
|
|
||||||
|
|
||||||
var pcache = &pidCache{}
|
|
||||||
|
|
||||||
func LookupUDPSocketProcess(srcPort uint16) *ProcInfo {
|
|
||||||
ss := findUDPSocket(srcPort)
|
|
||||||
if ss == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return pcache.lookup(ss.inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func LookupTCPSocketProcess(srcPort uint16, dstAddr net.IP, dstPort uint16) *ProcInfo {
|
|
||||||
ss := findTCPSocket(srcPort, dstAddr, dstPort)
|
|
||||||
if ss == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return pcache.lookup(ss.inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectionInfo struct {
|
|
||||||
pinfo *ProcInfo
|
|
||||||
local *socketAddr
|
|
||||||
remote *socketAddr
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ci *ConnectionInfo) String() string {
|
|
||||||
return fmt.Sprintf("%v %s %s", ci.pinfo, ci.local, ci.remote)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sa *socketAddr) parse(s string) error {
|
|
||||||
ipPort := strings.Split(s, ":")
|
|
||||||
if len(ipPort) != 2 {
|
|
||||||
return fmt.Errorf("badly formatted socket address field: %s", s)
|
|
||||||
}
|
|
||||||
ip, err := ParseIp(ipPort[0])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error parsing ip field [%s]: %v", ipPort[0], err)
|
|
||||||
}
|
|
||||||
port, err := ParsePort(ipPort[1])
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("error parsing port field [%s]: %v", ipPort[1], err)
|
|
||||||
}
|
|
||||||
sa.ip = ip
|
|
||||||
sa.port = port
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseIp(ip string) (net.IP, error) {
|
|
||||||
var result net.IP
|
|
||||||
dst, err := hex.DecodeString(ip)
|
|
||||||
if err != nil {
|
|
||||||
return result, fmt.Errorf("Error parsing IP: %s", err)
|
|
||||||
}
|
|
||||||
// Reverse byte order -- /proc/net/tcp etc. is little-endian
|
|
||||||
// TODO: Does this vary by architecture?
|
|
||||||
for i, j := 0, len(dst)-1; i < j; i, j = i+1, j-1 {
|
|
||||||
dst[i], dst[j] = dst[j], dst[i]
|
|
||||||
}
|
|
||||||
result = net.IP(dst)
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParsePort(port string) (uint16, error) {
|
|
||||||
p64, err := strconv.ParseInt(port, 16, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0, fmt.Errorf("Error parsing port: %s", err)
|
|
||||||
}
|
|
||||||
return uint16(p64), nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getConnections() ([]*ConnectionInfo, error) {
|
|
||||||
conns, err := readConntrack()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
resolveProcinfo(conns)
|
|
||||||
return conns, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func resolveProcinfo(conns []*ConnectionInfo) {
|
|
||||||
var sockets []*socketStatus
|
|
||||||
for _, line := range getSocketLines("tcp") {
|
|
||||||
if len(strings.TrimSpace(line)) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
ss := new(socketStatus)
|
|
||||||
if err := ss.parseLine(line); err != nil {
|
|
||||||
log.Warning("Unable to parse line [%s]: %v", line, err)
|
|
||||||
} else {
|
|
||||||
/*
|
|
||||||
pid := findPidForInode(ss.inode)
|
|
||||||
if pid > 0 {
|
|
||||||
ss.pid = pid
|
|
||||||
fmt.Println("Socket", ss)
|
|
||||||
sockets = append(sockets, ss)
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for _, ci := range conns {
|
|
||||||
ss := findContrackSocket(ci, sockets)
|
|
||||||
if ss == nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
pinfo := pcache.lookup(ss.inode)
|
|
||||||
if pinfo != nil {
|
|
||||||
ci.pinfo = pinfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func findContrackSocket(ci *ConnectionInfo, sockets []*socketStatus) *socketStatus {
|
|
||||||
for _, ss := range sockets {
|
|
||||||
if ss.local.port == ci.local.port && ss.remote.ip.Equal(ci.remote.ip) && ss.remote.port == ci.remote.port {
|
|
||||||
return ss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func readConntrack() ([]*ConnectionInfo, error) {
|
|
||||||
path := fmt.Sprintf("/proc/net/ip_conntrack")
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
var result []*ConnectionInfo
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
for _, line := range lines {
|
|
||||||
ci, err := parseConntrackLine(line)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
if ci != nil {
|
|
||||||
result = append(result, ci)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseConntrackLine(line string) (*ConnectionInfo, error) {
|
|
||||||
parts := strings.Fields(line)
|
|
||||||
if len(parts) < 8 || parts[0] != "tcp" || parts[3] != "ESTABLISHED" {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
local, err := conntrackAddr(parts[4], parts[6])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
remote, err := conntrackAddr(parts[5], parts[7])
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &ConnectionInfo{
|
|
||||||
local: local,
|
|
||||||
remote: remote,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func conntrackAddr(ip_str, port_str string) (*socketAddr, error) {
|
|
||||||
ip := net.ParseIP(stripLabel(ip_str))
|
|
||||||
if ip == nil {
|
|
||||||
return nil, errors.New("Could not parse IP: " + ip_str)
|
|
||||||
}
|
|
||||||
i64, err := strconv.Atoi(stripLabel(port_str))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return &socketAddr{
|
|
||||||
ip: ip,
|
|
||||||
port: uint16(i64),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func stripLabel(s string) string {
|
|
||||||
idx := strings.Index(s, "=")
|
|
||||||
if idx == -1 {
|
|
||||||
return s
|
|
||||||
}
|
|
||||||
return s[idx+1:]
|
|
||||||
}
|
|
@ -1,149 +0,0 @@
|
|||||||
package proc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"sync"
|
|
||||||
"syscall"
|
|
||||||
)
|
|
||||||
|
|
||||||
type ProcInfo struct {
|
|
||||||
Uid int
|
|
||||||
Pid int
|
|
||||||
loaded bool
|
|
||||||
ExePath string
|
|
||||||
CmdLine string
|
|
||||||
}
|
|
||||||
|
|
||||||
type pidCache struct {
|
|
||||||
cacheMap map[uint64]*ProcInfo
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pc *pidCache) lookup(inode uint64) *ProcInfo {
|
|
||||||
pc.lock.Lock()
|
|
||||||
defer pc.lock.Unlock()
|
|
||||||
pi, ok := pc.cacheMap[inode]
|
|
||||||
if ok && pi.loadProcessInfo() {
|
|
||||||
return pi
|
|
||||||
}
|
|
||||||
pc.cacheMap = loadCache()
|
|
||||||
pi, ok = pc.cacheMap[inode]
|
|
||||||
if ok && pi.loadProcessInfo() {
|
|
||||||
return pi
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func loadCache() map[uint64]*ProcInfo {
|
|
||||||
cmap := make(map[uint64]*ProcInfo)
|
|
||||||
for _, n := range readdir("/proc") {
|
|
||||||
pid := toPid(n)
|
|
||||||
if pid != 0 {
|
|
||||||
pinfo := &ProcInfo{Pid: pid}
|
|
||||||
for _, inode := range inodesFromPid(pid) {
|
|
||||||
cmap[inode] = pinfo
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return cmap
|
|
||||||
}
|
|
||||||
|
|
||||||
func toPid(name string) int {
|
|
||||||
pid, err := strconv.ParseUint(name, 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
fdpath := fmt.Sprintf("/proc/%d/fd", pid)
|
|
||||||
fi, err := os.Stat(fdpath)
|
|
||||||
if err != nil {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
if !fi.IsDir() {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return (int)(pid)
|
|
||||||
}
|
|
||||||
|
|
||||||
func inodesFromPid(pid int) []uint64 {
|
|
||||||
var inodes []uint64
|
|
||||||
fdpath := fmt.Sprintf("/proc/%d/fd", pid)
|
|
||||||
for _, n := range readdir(fdpath) {
|
|
||||||
if link, err := os.Readlink(path.Join(fdpath, n)); err != nil {
|
|
||||||
if !os.IsNotExist(err) {
|
|
||||||
log.Warning("Error reading link %s: %v", n, err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if inode := extractSocket(link); inode > 0 {
|
|
||||||
inodes = append(inodes, inode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return inodes
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractSocket(name string) uint64 {
|
|
||||||
if !strings.HasPrefix(name, "socket:[") || !strings.HasSuffix(name, "]") {
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
val := name[8 : len(name)-1]
|
|
||||||
inode, err := strconv.ParseUint(val, 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error parsing inode value from %s: %v", name, err)
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
return inode
|
|
||||||
}
|
|
||||||
|
|
||||||
func readdir(dir string) []string {
|
|
||||||
d, err := os.Open(dir)
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error opening directory %s: %v", dir, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer d.Close()
|
|
||||||
names, err := d.Readdirnames(0)
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error reading directory names from %s: %v", dir, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return names
|
|
||||||
}
|
|
||||||
|
|
||||||
func (pi *ProcInfo) loadProcessInfo() bool {
|
|
||||||
if pi.loaded {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
exePath, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pi.Pid))
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error reading exe link for pid %d: %v", pi.Pid, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
bs, err := ioutil.ReadFile(fmt.Sprintf("/proc/%d/cmdline", pi.Pid))
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error reading cmdline for pid %d: %v", pi.Pid, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for i, b := range bs {
|
|
||||||
if b == 0 {
|
|
||||||
bs[i] = byte(' ')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
finfo, err := os.Stat(fmt.Sprintf("/proc/%d", pi.Pid))
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Could not stat /proc/%d: %v", pi.Pid, err)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
sys := finfo.Sys().(*syscall.Stat_t)
|
|
||||||
pi.Uid = int(sys.Uid)
|
|
||||||
pi.ExePath = exePath
|
|
||||||
pi.CmdLine = string(bs)
|
|
||||||
pi.loaded = true
|
|
||||||
return true
|
|
||||||
}
|
|
@ -1,99 +0,0 @@
|
|||||||
package proc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type socketAddr struct {
|
|
||||||
ip net.IP
|
|
||||||
port uint16
|
|
||||||
}
|
|
||||||
|
|
||||||
func (sa socketAddr) String() string {
|
|
||||||
return fmt.Sprintf("%v:%d", sa.ip, sa.port)
|
|
||||||
}
|
|
||||||
|
|
||||||
type socketStatus struct {
|
|
||||||
local socketAddr
|
|
||||||
remote socketAddr
|
|
||||||
uid int
|
|
||||||
inode uint64
|
|
||||||
line string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *socketStatus) String() string {
|
|
||||||
return fmt.Sprintf("%s -> %s uid=%d inode=%d", ss.local, ss.remote, ss.uid, ss.inode)
|
|
||||||
}
|
|
||||||
|
|
||||||
func findUDPSocket(srcPort uint16) *socketStatus {
|
|
||||||
return findSocket("udp", func(ss socketStatus) bool {
|
|
||||||
return ss.local.port == srcPort
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func findTCPSocket(srcPort uint16, dstAddr net.IP, dstPort uint16) *socketStatus {
|
|
||||||
return findSocket("tcp", func(ss socketStatus) bool {
|
|
||||||
return ss.remote.port == dstPort && ss.remote.ip.Equal(dstAddr) && ss.local.port == srcPort
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func findSocket(proto string, matcher func(socketStatus) bool) *socketStatus {
|
|
||||||
var ss socketStatus
|
|
||||||
for _, line := range getSocketLines(proto) {
|
|
||||||
if len(line) == 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if err := ss.parseLine(line); err != nil {
|
|
||||||
log.Warning("Unable to parse line from /proc/net/%s [%s]: %v", proto, line, err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if matcher(ss) {
|
|
||||||
ss.line = line
|
|
||||||
return &ss
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ss *socketStatus) parseLine(line string) error {
|
|
||||||
fs := strings.Fields(line)
|
|
||||||
if len(fs) < 10 {
|
|
||||||
return errors.New("insufficient fields")
|
|
||||||
}
|
|
||||||
if err := ss.local.parse(fs[1]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
if err := ss.remote.parse(fs[2]); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
uid, err := strconv.ParseUint(fs[7], 10, 32)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ss.uid = int(uid)
|
|
||||||
inode, err := strconv.ParseUint(fs[9], 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
ss.inode = inode
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getSocketLines(proto string) []string {
|
|
||||||
path := fmt.Sprintf("/proc/net/%s", proto)
|
|
||||||
data, err := ioutil.ReadFile(path)
|
|
||||||
if err != nil {
|
|
||||||
log.Warning("Error reading %s: %v", path, err)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
lines := strings.Split(string(data), "\n")
|
|
||||||
if len(lines) > 0 {
|
|
||||||
lines = lines[1:]
|
|
||||||
}
|
|
||||||
return lines
|
|
||||||
}
|
|
Loading…
Reference in new issue