|
|
|
package procsnitch
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/binary"
|
|
|
|
"encoding/hex"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"github.com/op/go-logging"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"unsafe"
|
|
|
|
)
|
|
|
|
|
|
|
|
var log = logging.MustGetLogger("go-procsockets")
|
|
|
|
var isLittleEndian = -1
|
|
|
|
|
|
|
|
// SetLogger allows setting a custom go-logging instance
|
|
|
|
func SetLogger(logger *logging.Logger) {
|
|
|
|
log = logger
|
|
|
|
}
|
|
|
|
|
|
|
|
var pcache = &pidCache{}
|
|
|
|
|
|
|
|
// ProcInfo represents an api that can be used to query process information about
|
|
|
|
// the far side of a network connection
|
|
|
|
// Note: this can aid in the construction of unit tests.
|
|
|
|
type ProcInfo interface {
|
|
|
|
LookupTCPSocketProcess(srcPort uint16, dstAddr net.IP, dstPort uint16) *Info
|
|
|
|
LookupUNIXSocketProcess(socketFile string) *Info
|
|
|
|
LookupUDPSocketProcess(srcPort uint16) *Info
|
|
|
|
}
|
|
|
|
|
|
|
|
// SystemProcInfo represents our real system ProcInfo api.
|
|
|
|
type SystemProcInfo struct {
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupTCPSocketProcess returns the process information for a given TCP connection.
|
|
|
|
func (r SystemProcInfo) LookupTCPSocketProcess(srcPort uint16, dstAddr net.IP, dstPort uint16) *Info {
|
|
|
|
return LookupTCPSocketProcess(srcPort, dstAddr, dstPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupUNIXSocketProcess returns the process information for a given UNIX socket connection.
|
|
|
|
func (r SystemProcInfo) LookupUNIXSocketProcess(socketFile string) *Info {
|
|
|
|
return LookupUNIXSocketProcess(socketFile)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupUDPSocketProcess returns the process information for a given UDP socket connection.
|
|
|
|
func (r SystemProcInfo) LookupUDPSocketProcess(srcPort uint16) *Info {
|
|
|
|
return LookupUDPSocketProcess(srcPort)
|
|
|
|
}
|
|
|
|
|
|
|
|
// FindProcessForConnection returns the process information for a given connection.
|
|
|
|
// So far only TCP and UNIX domain socket connections are supported.
|
|
|
|
func FindProcessForConnection(conn net.Conn, procInfo ProcInfo) *Info {
|
|
|
|
var info *Info
|
|
|
|
if conn.LocalAddr().Network() == "tcp" {
|
|
|
|
fields := strings.Split(conn.RemoteAddr().String(), ":")
|
|
|
|
dstPortStr := fields[1]
|
|
|
|
fields = strings.Split(conn.LocalAddr().String(), ":")
|
|
|
|
dstIP := net.ParseIP(fields[0])
|
|
|
|
srcP, _ := strconv.ParseUint(dstPortStr, 10, 16)
|
|
|
|
dstP, _ := strconv.ParseUint(fields[1], 10, 16)
|
|
|
|
info = procInfo.LookupTCPSocketProcess(uint16(srcP), dstIP, uint16(dstP))
|
|
|
|
} else if conn.LocalAddr().Network() == "unix" {
|
|
|
|
info = procInfo.LookupUNIXSocketProcess(conn.LocalAddr().String())
|
|
|
|
}
|
|
|
|
return info
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupICMPSocketProcessAll searches for a ICMP socket a given source host, destination IP, and type
|
|
|
|
func LookupICMPSocketProcessAll(srcAddr net.IP, dstAddr net.IP, code int, custdata []string) *Info {
|
|
|
|
ss := findICMPSocketAll(srcAddr, dstAddr, code, custdata)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupUDPSocketProcessAll searches for a UDP socket a given source port, destination IP, and destination port - AND source destination
|
|
|
|
func LookupUDPSocketProcessAll(srcAddr net.IP, srcPort uint16, dstAddr net.IP, dstPort uint16, custdata []string, strictness int) *Info {
|
|
|
|
ss := findUDPSocketAll(srcAddr, srcPort, dstAddr, dstPort, custdata, strictness)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupUDPSocketProcess searches for a UDP socket with a source port
|
|
|
|
func LookupUDPSocketProcess(srcPort uint16) *Info {
|
|
|
|
ss := findUDPSocket(srcPort)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupTCPSocketProcessAll searches for a TCP socket a given source port, destination IP, and destination port - AND source destination
|
|
|
|
func LookupTCPSocketProcessAll(srcAddr net.IP, srcPort uint16, dstAddr net.IP, dstPort uint16, custdata []string) *Info {
|
|
|
|
ss := findTCPSocketAll(srcAddr, srcPort, dstAddr, dstPort, custdata)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupTCPSocketProcess searches for a TCP socket with a given source port, destination IP, and destination port
|
|
|
|
func LookupTCPSocketProcess(srcPort uint16, dstAddr net.IP, dstPort uint16) *Info {
|
|
|
|
ss := findTCPSocket(srcPort, dstAddr, dstPort)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
// LookupUNIXSocketProcess searches for a UNIX domain socket with a given filename
|
|
|
|
func LookupUNIXSocketProcess(socketFile string) *Info {
|
|
|
|
ss := findUNIXSocket(socketFile)
|
|
|
|
if ss == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return pcache.lookup(ss.inode)
|
|
|
|
}
|
|
|
|
|
|
|
|
type connectionInfo struct {
|
|
|
|
pinfo *Info
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParseIP parses a string ip to a net.IP
|
|
|
|
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?
|
|
|
|
if isLittleEndian == -1 {
|
|
|
|
setEndian()
|
|
|
|
}
|
|
|
|
|
|
|
|
if len(dst) != 4 && len(dst) != 16 {
|
|
|
|
return result, errors.New("Unsupported address type (not IPv4 or IPv16)")
|
|
|
|
}
|
|
|
|
|
|
|
|
if isLittleEndian > 0 {
|
|
|
|
for i := 0; i < len(dst)/4; i++ {
|
|
|
|
start, end := i*4, (i+1)*4
|
|
|
|
word := dst[start:end]
|
|
|
|
lval := binary.LittleEndian.Uint32(word)
|
|
|
|
binary.BigEndian.PutUint32(dst[start:], lval)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* if len(dst) == 16 {
|
|
|
|
dst2 := []byte{dst[3], dst[2], dst[1], dst[0], dst[7], dst[6], dst[5], dst[4], dst[11], dst[10], dst[9], dst[8], dst[15], dst[14], dst[13], dst[12]}
|
|
|
|
return net.IP(dst2), nil
|
|
|
|
}
|
|
|
|
for i, j := 0, len(dst)-1; i < j; i, j = i+1, j-1 {
|
|
|
|
dst[i], dst[j] = dst[j], dst[i]
|
|
|
|
} */
|
|
|
|
|
|
|
|
return net.IP(dst), nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ParsePort parses a base16 port represented as a string to a uint16
|
|
|
|
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.Warningf("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(ipStr, portStr string) (*socketAddr, error) {
|
|
|
|
ip := net.ParseIP(stripLabel(ipStr))
|
|
|
|
if ip == nil {
|
|
|
|
return nil, errors.New("Could not parse IP: " + ipStr)
|
|
|
|
}
|
|
|
|
i64, err := strconv.Atoi(stripLabel(portStr))
|
|
|
|
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:]
|
|
|
|
}
|
|
|
|
|
|
|
|
// stolen from github.com/virtao/GoEndian
|
|
|
|
const INT_SIZE int = int(unsafe.Sizeof(0))
|
|
|
|
|
|
|
|
func setEndian() {
|
|
|
|
var i int = 0x1
|
|
|
|
bs := (*[INT_SIZE]byte)(unsafe.Pointer(&i))
|
|
|
|
if bs[0] == 0 {
|
|
|
|
isLittleEndian = 0
|
|
|
|
} else {
|
|
|
|
isLittleEndian = 1
|
|
|
|
}
|
|
|
|
}
|